#ifndef CORE_ARRAY_STRING_HPP #define CORE_ARRAY_STRING_HPP #include "math/Math.hpp" #include "utils/Check.hpp" #include "utils/Types.hpp" #include "utils/Utility.hpp" namespace Core { template constexpr int stringLength(const T* c) { int i = 0; while(*c != '\0') { c++; i++; } return i; } template class ArrayString final { int length; u32 hash; static_assert(N > 0, "size of array string must be positive"); CharType data[static_cast(N)]; public: ArrayString() : length(0), hash(0) { data[0] = '\0'; } bool operator==(const CharType* s) const { for(int i = 0; i < length; i++, s++) { if(*s == '\0' && *s != data[i]) { return false; } } return *s == '\0'; } template bool operator==(const ArrayString& other) const { if(length != other.getLength()) { return false; } for(int i = 0; i < length; i++) { if(data[i] != other[i]) { return false; } } return true; } bool operator!=(const CharType* s) const { return !((*this) == s); } template bool operator!=(const ArrayString& other) const { return !((*this) == other); } CharType operator[](int index) const { return data[index]; } int getLength() const { return length; } constexpr int getCapacity() const { return N - 1; } check_return Error append(char c) { return add(static_cast(c)); } check_return Error append(signed char c) { return append(static_cast(c)); } check_return Error append(unsigned char c) { return append(static_cast(c)); } check_return Error append(wchar_t c) { return append(static_cast(c)); } check_return Error append(c32 c) { if constexpr(IsSame) { char buffer[5]; unicodeToChar(c, buffer); return append(static_cast(buffer)); } else { return add(c); } } check_return Error append(const char* s) { if constexpr(IsSame) { // stringLength as s could be some part of data for(int i = stringLength(s); i > 0; i--) { CORE_RETURN_ERROR(append(*(s++))); } return Error::NONE; } else { while(true) { c32 u = 0; CORE_RETURN_ERROR(readUnicode(u, s)); if(u == 0) { return Error::NONE; } CORE_RETURN_ERROR(append(u)); } } } check_return Error append(const c32* s) { // stringLength as s could be some part of data for(int i = stringLength(s); i > 0; i--) { CORE_RETURN_ERROR(append(*(s++))); } return Error::NONE; } check_return Error append(const signed char* s) { return append(reinterpret_cast(s)); } check_return Error append(const unsigned char* s) { return append(reinterpret_cast(s)); } check_return Error append(signed short s) { return convertAppend(s); } check_return Error append(unsigned short s) { return convertAppend(s); } check_return Error append(signed int i) { return convertAppend(i); } check_return Error append(unsigned int i) { return convertAppend(i); } check_return Error append(signed long l) { return convertAppend(l); } check_return Error append(unsigned long l) { return convertAppend(l); } check_return Error append(signed long long ll) { return convertAppend(ll); } check_return Error append(unsigned long long ll) { return convertAppend(ll); } check_return Error append(float f) { return convertAppend(static_cast(f)); } check_return Error append(double d) { return convertAppend(d); } check_return Error append(long double ld) { return convertAppend(ld); } check_return Error append(bool b) { return b ? append("true") : append("false"); } check_return Error append(Error e) { return append(getErrorName(e)); } template check_return Error append(const T& t) { return t.toString(*this); } template check_return Error toString(ArrayString& s) const { if constexpr(IsSame && !IsSame) { // utf32 to utf8 return s.append(data); } int l = length; // length changes if &s == this for(int i = 0; i < l; i++) { CORE_RETURN_ERROR(s.append(data[i])); } return Error::NONE; } void clear() { length = 0; hash = 0; data[0] = '\0'; } u32 hashCode() const { return hash; } check_return Error print() const { if constexpr(IsSame) { for(int i = 0; i < length; i++) { CORE_RETURN_ERROR(Core::putChar(data[i])); } return Error::NONE; } else { for(int i = 0; i < length; i++) { c32 c = data[i]; if(c < (1 << 7)) { CORE_RETURN_ERROR(printChar(c, 0, 0x7F, 0x0)); } else if(c < (1 << 11)) { CORE_RETURN_ERROR(printChar(c, 6, 0x1F, 0xC0)); CORE_RETURN_ERROR(printChar(c, 0, 0x3F, 0x80)); } else if(c < (1 << 16)) { CORE_RETURN_ERROR(printChar(c, 12, 0x0F, 0xE0)); CORE_RETURN_ERROR(printChar(c, 6, 0x3F, 0x80)); CORE_RETURN_ERROR(printChar(c, 0, 0x3F, 0x80)); } else if(c < (1 << 21)) { CORE_RETURN_ERROR(printChar(c, 18, 0x07, 0xF0)); CORE_RETURN_ERROR(printChar(c, 12, 0x3F, 0x80)); CORE_RETURN_ERROR(printChar(c, 6, 0x3F, 0x80)); CORE_RETURN_ERROR(printChar(c, 0, 0x3F, 0x80)); } } return Error::NONE; } } check_return Error printLine() const { CORE_RETURN_ERROR(print()); CORE_RETURN_ERROR(Core::putChar('\n')); return Error::NONE; } template check_return Error format(Args&&... args) { ArrayString s; Error e = formatBuffer(s, 0, Core::forward(args)...); if(e == Error::NONE || e == Error::CAPACITY_REACHED) { *this = s; } return e; } template bool startsWidth(const ArrayString& other, int from = 0) const { if(from + other.getLength() > length) { return false; } for(int i = 0; i < other.getLength(); i++) { if(data[from + i] != other[i]) { return false; } } return true; } template int search(const ArrayString& other, int from = 0) const { for(int i = from; i < length; i++) { if(startsWidth(other, i)) { return i; } } return -1; } template bool contains(const ArrayString& other, int from = 0) const { return search(other, from) >= 0; } int search(CharType u, int from = 0) const { for(int i = from; i < length; i++) { if(data[i] == u) { return i; } } return -1; } bool contains(CharType u, int from = 0) const { return search(u, from) >= 0; } ArrayString substring(int from, int to) const { from = Math::max(from, 0); to = Math::min(to, length - 1); ArrayString s; for(int i = from; i <= to; i++) { (void)s.append(data[i]); } return s; } ArrayString substring(int from = 0) const { return substring(from, length - 1); } template check_return Error replace(const ArrayString& search, const ArrayString& replace) { ArrayString s; int i = 0; while(i < length) { if(startsWidth(search, i)) { CORE_RETURN_ERROR(s.append(replace)); i += search.getLength(); } else { CORE_RETURN_ERROR(s.append(data[i])); i++; } } *this = s; return Error::NONE; } void replace(CharType search, CharType replace) { hash = 0; for(int i = 0; i < length; i++) { if(data[i] == search) { data[i] = replace; } addToHash(static_cast(data[i])); } } operator const CharType*() const { return data; } private: Error add(CharType c) { if(length >= N - 1) { return Error::CAPACITY_REACHED; } data[length++] = c; data[length] = '\0'; addToHash(static_cast(c)); return Error::NONE; } template void unicodeToChar(c32 c, char (&buffer)[L]) { static_assert(L >= 5, "to small char buffer"); if(c < (1 << 7)) { buffer[0] = static_cast(((c >> 0) & 0x7F) | 0x0); buffer[1] = '\0'; } else if(c < (1 << 11)) { buffer[0] = static_cast(((c >> 6) & 0x1F) | 0xC0); buffer[1] = static_cast(((c >> 0) & 0x3F) | 0x80); buffer[2] = '\0'; } else if(c < (1 << 16)) { buffer[0] = static_cast(((c >> 12) & 0x0F) | 0xE0); buffer[1] = static_cast(((c >> 6) & 0x3F) | 0x80); buffer[2] = static_cast(((c >> 0) & 0x3F) | 0x80); buffer[3] = '\0'; } else if(c < (1 << 21)) { buffer[0] = static_cast(((c >> 18) & 0x07) | 0xF0); buffer[1] = static_cast(((c >> 12) & 0x3F) | 0x80); buffer[2] = static_cast(((c >> 6) & 0x3F) | 0x80); buffer[3] = static_cast(((c >> 0) & 0x3F) | 0x80); buffer[4] = '\0'; } else { buffer[0] = '\0'; } } check_return static Error printChar(c32 u, u32 shift, u32 a, u32 o) { return Core::putChar(static_cast(((u >> shift) & a) | o)); } static c32 read(const char*& s) { if(*s == '\0') { return 0; } return static_cast(*(s++)); } static Error readUnicode(c32& u, const char*& s) { u = read(s); if((u & 0x80) == 0) { return Error::NONE; } if((u & 0xE0) == 0xC0) { c32 u2 = read(s); if(u2 == 0) { return Error::INVALID_CHAR; } u = ((u & 0x1F) << 6) | (u2 & 0x3F); return Error::NONE; } else if((u & 0xF0) == 0xE0) { c32 u2 = read(s); c32 u3 = read(s); if(u2 == 0 || u3 == 0) { return Error::INVALID_CHAR; } u = ((u & 0xF) << 12) | ((u2 & 0x3F) << 6) | (u3 & 0x3F); return Error::NONE; } else if((u & 0xF8) == 0xF0) { c32 u2 = read(s); c32 u3 = read(s); c32 u4 = read(s); if(u2 == 0 || u3 == 0 || u4 == 0) { return Error::INVALID_CHAR; } u = ((u & 0x07) << 18) | ((u2 & 0x3F) << 12) | ((u3 & 0x3F) << 6) | (u4 & 0x3F); return Error::NONE; } return Error::INVALID_CHAR; } void addToHash(c32 u) { hash = static_cast(2120251889) * hash + static_cast(u); } template check_return Error formatBuffer(ArrayString& s, int index, const T& t, Args&&... args) { while(index < length) { CharType u = data[index++]; if(u == '#') { if(index >= length || (index < length && data[index] != '#')) { break; } index++; } CORE_RETURN_ERROR(s.append(u)); } CORE_RETURN_ERROR(s.append(t)); return formatBuffer(s, index, Core::forward(args)...); } check_return Error formatBuffer(ArrayString& s, int index) { while(index < length) { CORE_RETURN_ERROR(s.append(data[index++])); } return Error::NONE; } template check_return Error convertAppend(T t) { char buffer[64]; CORE_RETURN_ERROR(Core::toString(t, buffer, CORE_SIZE(buffer))); return append(static_cast(buffer)); } }; template check_return Error toString(String& s, const Iterable& i) { CORE_RETURN_ERROR(s.append("[")); auto current = i.begin(); auto end = i.end(); while(current != end) { CORE_RETURN_ERROR(s.append(*current)); ++current; if(current != end) { CORE_RETURN_ERROR(s.append(", ")); } } return s.append("]"); } template using String8 = ArrayString; template using String32 = ArrayString; } template bool operator==(const CharType* cs, const Core::ArrayString& s) { return s == cs; } template bool operator!=(const CharType* cs, const Core::ArrayString& s) { return s != cs; } #endif