8 Commits ffa6239baa ... 817f8eb1bf

Author SHA1 Message Date
  Kajetan Johannes Hammerle 817f8eb1bf Use requires for class check 2 weeks ago
  Kajetan Johannes Hammerle 01a2e27135 Move char string code into cpp 2 weeks ago
  Kajetan Johannes Hammerle 4dafb76e04 Pass storage to char string 2 weeks ago
  Kajetan Johannes Hammerle 9494a24124 Separate char and char32_t string 2 weeks ago
  Kajetan Johannes Hammerle 8f50ca109d Optimize string comparison 3 weeks ago
  Kajetan Johannes Hammerle de7a50bd49 Optimize string length 3 weeks ago
  Kajetan Johannes Hammerle e3e76a1a90 Always stop after null character 3 weeks ago
  Kajetan Johannes Hammerle 8595aeaf61 Move unicode reading into static function 3 weeks ago
5 changed files with 552 additions and 129 deletions
  1. 1 0
      .gitignore
  2. 1 0
      CMakeLists.txt
  3. 216 110
      include/core/utils/ArrayString.hpp
  4. 306 0
      src/ArrayString.cpp
  5. 28 19
      test/modules/ArrayStringTests.cpp

+ 1 - 0
.gitignore

@@ -4,3 +4,4 @@ install
 profile
 compiler
 .cache
+*.swp

+ 1 - 0
CMakeLists.txt

@@ -24,6 +24,7 @@ set(SRC
     "src/Mutex.cpp"
     "src/FileReader.cpp"
     "src/ErrorSimulator.cpp"
+    "src/ArrayString.cpp"
 )
 
 set(SRC_TESTS

+ 216 - 110
include/core/utils/ArrayString.hpp

@@ -3,43 +3,188 @@
 
 #include "core/math/Math.hpp"
 #include "core/utils/Check.hpp"
+#include "core/utils/Error.hpp"
+#include "core/utils/Meta.hpp"
 #include "core/utils/Types.hpp"
 #include "core/utils/Utility.hpp"
 
 namespace Core {
     template<typename T>
     constexpr int stringLength(const T* c) {
-        int i = 0;
-        while(*c != '\0') {
-            c++;
-            i++;
-        }
-        return i;
+        const T* i = c + 1;
+        while(*(c++) != '\0') {}
+        return static_cast<int>(c - i);
     }
 
-    template<int N, typename CharType>
-    class ArrayString final {
+    Error readUnicode(c32& u, const char*& s);
+
+    template<int N>
+    class Char32String;
+
+    class CharString {
+    protected:
+        i32 length;
+        i32 capacity;
+        u32 hash;
+        char* data;
+
+    public:
+        CharString(char* buffer, i32 bufferSize);
+        CharString(const CharString&) = delete;
+        CharString& operator=(const CharString&) = delete;
+
+        check_return Error copyFrom(const CharString& s);
+        bool operator==(const char* s) const;
+        bool operator==(const CharString& other) const;
+        bool operator!=(const char* s) const;
+        bool operator!=(const CharString& other) const;
+        char operator[](int index) const;
+        int getLength() const;
+        int getCapacity() const;
+        check_return Error append(char c);
+        check_return Error append(signed char c);
+        check_return Error append(unsigned char c);
+        check_return Error append(wchar_t c);
+        check_return Error append(c32 c);
+        check_return Error append(const char* s);
+        check_return Error append(const c32* s);
+        check_return Error append(const signed char* s);
+        check_return Error append(const unsigned char* s);
+        check_return Error append(bool b);
+        check_return Error append(Error e);
+
+        template<typename T>
+        check_return Error append(const T& t) {
+            if constexpr(requires { t.toString(*this); }) {
+                return t.toString(*this);
+            } else {
+                char buffer[64];
+                CORE_RETURN_ERROR(Core::toString(t, buffer, CORE_SIZE(buffer)));
+                return append(static_cast<const char*>(buffer));
+            }
+        }
+
+        check_return Error toString(CharString& s) const;
+
+        template<int L>
+        check_return Error toString(Char32String<L>& s) const {
+            return s.append(static_cast<const char*>(data));
+        }
+
+        void clear();
+        u32 hashCode() const;
+        check_return Error print() const;
+        check_return Error printLine() const;
+
+        template<typename... Args>
+        check_return Error format(CharString& s, Args&&... args) {
+            Error e = formatBuffer(s, 0, Core::forward<Args>(args)...);
+            if(e == Error::NONE) {
+                return copyFrom(s);
+            } else if(e == Error::CAPACITY_REACHED) {
+                (void)copyFrom(s);
+            }
+            return e;
+        }
+
+        bool startsWidth(const CharString& other, int from = 0) const;
+        int search(const CharString& other, int from = 0) const;
+        bool contains(const CharString& other, int from = 0) const;
+        int search(char u, int from = 0) const;
+        bool contains(char u, int from = 0) const;
+        check_return Error substring(CharString& s, int from, int to) const;
+        check_return Error substring(CharString& s, int from = 0) const;
+        check_return Error replace(CharString& s, const CharString& search,
+                                   const CharString& replace);
+        void replace(char search, char replace);
+        operator const char*() const;
+
+    private:
+        void addToHash(c32 u);
+
+        template<typename T, typename... Args>
+        check_return Error formatBuffer(CharString& s, int index, const T& t,
+                                        Args&&... args) {
+            while(index < length) {
+                char 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>(args)...);
+        }
+
+        check_return Error formatBuffer(CharString& s, int index);
+    };
+
+    template<int N>
+    class ArrayCharString final : public CharString {
+        static_assert(N > 0, "size of array string must be positive");
+        char data[static_cast<unsigned int>(N)];
+
+    public:
+        ArrayCharString() : CharString(data, N) {
+        }
+
+        ArrayCharString(const ArrayCharString& other) : CharString(data, N) {
+            Core::memoryCopy(data, other.data, N);
+            length = other.length;
+            hash = other.hash;
+        }
+
+        ArrayCharString& operator=(const ArrayCharString& other) {
+            if(this != &other) {
+                Core::memoryCopy(data, other.data, N);
+                length = other.length;
+                hash = other.hash;
+            }
+            return *this;
+        }
+
+        template<typename... Args>
+        check_return Error format(Args&&... args) {
+            ArrayCharString s;
+            return CharString::format(s, Core::forward<Args>(args)...);
+        }
+
+        check_return Error replace(const CharString& search,
+                                   const CharString& replace) {
+            ArrayCharString s;
+            return CharString::replace(s, search, replace);
+        }
+
+        using CharString::replace;
+    };
+
+    template<int N>
+    class Char32String final {
         int length;
         u32 hash;
         static_assert(N > 0, "size of array string must be positive");
-        CharType data[static_cast<unsigned int>(N)];
+        c32 data[static_cast<unsigned int>(N)];
 
     public:
-        ArrayString() : length(0), hash(0) {
+        Char32String() : 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;
-                }
+        bool operator==(const c32* s) const {
+            const c32* p = data;
+            while(*s == *p && *s != '\0') {
+                s++;
+                p++;
             }
-            return *s == '\0';
+            return *s == *p;
         }
 
         template<int L>
-        bool operator==(const ArrayString<L, CharType>& other) const {
+        bool operator==(const Char32String<L>& other) const {
             if(length != other.getLength()) {
                 return false;
             }
@@ -51,16 +196,16 @@ namespace Core {
             return true;
         }
 
-        bool operator!=(const CharType* s) const {
+        bool operator!=(const c32* s) const {
             return !((*this) == s);
         }
 
         template<int L>
-        bool operator!=(const ArrayString<L, CharType>& other) const {
+        bool operator!=(const Char32String<L>& other) const {
             return !((*this) == other);
         }
 
-        CharType operator[](int index) const {
+        c32 operator[](int index) const {
             return data[index];
         }
 
@@ -73,7 +218,7 @@ namespace Core {
         }
 
         check_return Error append(char c) {
-            return add(static_cast<CharType>(c));
+            return add(static_cast<c32>(c));
         }
 
         check_return Error append(signed char c) {
@@ -89,7 +234,7 @@ namespace Core {
         }
 
         check_return Error append(c32 c) {
-            if constexpr(IsSame<CharType, char>) {
+            if constexpr(IsSame<c32, char>) {
                 char buffer[5];
                 unicodeToChar(c, buffer);
                 return append(static_cast<const char*>(buffer));
@@ -99,21 +244,13 @@ namespace Core {
         }
 
         check_return Error append(const char* s) {
-            if constexpr(IsSame<CharType, char>) {
-                // 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));
+            while(true) {
+                c32 u = 0;
+                CORE_RETURN_ERROR(readUnicode(u, s));
+                if(u == 0) {
+                    return Error::NONE;
                 }
+                CORE_RETURN_ERROR(append(u));
             }
         }
 
@@ -190,12 +327,16 @@ namespace Core {
             return t.toString(*this);
         }
 
-        template<int L, typename C>
-        check_return Error toString(ArrayString<L, C>& s) const {
-            if constexpr(IsSame<CharType, char> && !IsSame<C, char>) {
-                // utf32 to utf8
-                return s.append(data);
+        check_return Error toString(CharString& s) const {
+            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;
+        }
+
+        template<int L>
+        check_return Error toString(Char32String<L>& s) const {
             int l = length; // length changes if &s == this
             for(int i = 0; i < l; i++) {
                 CORE_RETURN_ERROR(s.append(data[i]));
@@ -214,7 +355,7 @@ namespace Core {
         }
 
         check_return Error print() const {
-            if constexpr(IsSame<CharType, char>) {
+            if constexpr(IsSame<c32, char>) {
                 for(int i = 0; i < length; i++) {
                     CORE_RETURN_ERROR(Core::putChar(data[i]));
                 }
@@ -250,7 +391,7 @@ namespace Core {
 
         template<typename... Args>
         check_return Error format(Args&&... args) {
-            ArrayString s;
+            Char32String s;
             Error e = formatBuffer(s, 0, Core::forward<Args>(args)...);
             if(e == Error::NONE || e == Error::CAPACITY_REACHED) {
                 *this = s;
@@ -259,8 +400,7 @@ namespace Core {
         }
 
         template<int L>
-        bool startsWidth(const ArrayString<L, CharType>& other,
-                         int from = 0) const {
+        bool startsWidth(const Char32String<L>& other, int from = 0) const {
             if(from > length - other.getLength()) {
                 return false;
             }
@@ -273,7 +413,7 @@ namespace Core {
         }
 
         template<int L>
-        int search(const ArrayString<L, CharType>& other, int from = 0) const {
+        int search(const Char32String<L>& other, int from = 0) const {
             for(int i = from; i < length; i++) {
                 if(startsWidth(other, i)) {
                     return i;
@@ -283,12 +423,11 @@ namespace Core {
         }
 
         template<int L>
-        bool contains(const ArrayString<L, CharType>& other,
-                      int from = 0) const {
+        bool contains(const Char32String<L>& other, int from = 0) const {
             return search(other, from) >= 0;
         }
 
-        int search(CharType u, int from = 0) const {
+        int search(c32 u, int from = 0) const {
             for(int i = from; i < length; i++) {
                 if(data[i] == u) {
                     return i;
@@ -297,28 +436,28 @@ namespace Core {
             return -1;
         }
 
-        bool contains(CharType u, int from = 0) const {
+        bool contains(c32 u, int from = 0) const {
             return search(u, from) >= 0;
         }
 
-        ArrayString substring(int from, int to) const {
+        Char32String substring(int from, int to) const {
             from = Math::max(from, 0);
             to = Math::min(to, length - 1);
-            ArrayString s;
+            Char32String s;
             for(int i = from; i <= to; i++) {
                 (void)s.append(data[i]);
             }
             return s;
         }
 
-        ArrayString substring(int from = 0) const {
+        Char32String substring(int from = 0) const {
             return substring(from, length - 1);
         }
 
         template<int L1, int L2>
-        check_return Error replace(const ArrayString<L1, CharType>& search,
-                                   const ArrayString<L2, CharType>& replace) {
-            ArrayString<N, CharType> s;
+        check_return Error replace(const Char32String<L1>& search,
+                                   const Char32String<L2>& replace) {
+            Char32String<N> s;
             int i = 0;
             while(i < length) {
                 if(startsWidth(search, i)) {
@@ -333,7 +472,7 @@ namespace Core {
             return Error::NONE;
         }
 
-        void replace(CharType search, CharType replace) {
+        void replace(c32 search, c32 replace) {
             hash = 0;
             for(int i = 0; i < length; i++) {
                 if(data[i] == search) {
@@ -343,12 +482,12 @@ namespace Core {
             }
         }
 
-        operator const CharType*() const {
+        operator const c32*() const {
             return data;
         }
 
     private:
-        Error add(CharType c) {
+        Error add(c32 c) {
             if(length >= N - 1) {
                 return Error::CAPACITY_REACHED;
             }
@@ -387,56 +526,15 @@ namespace Core {
             return Core::putChar(static_cast<int>(((u >> shift) & a) | o));
         }
 
-        static c32 read(const char*& s) {
-            if(*s == '\0') {
-                return 0;
-            }
-            return static_cast<c32>(*(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<u32>(2120251889) * hash + static_cast<u32>(u);
         }
 
         template<typename T, typename... Args>
-        check_return Error formatBuffer(ArrayString& s, int index, const T& t,
+        check_return Error formatBuffer(Char32String& s, int index, const T& t,
                                         Args&&... args) {
             while(index < length) {
-                CharType u = data[index++];
+                c32 u = data[index++];
                 if(u == '#') {
                     if(index >= length ||
                        (index < length && data[index] != '#')) {
@@ -450,7 +548,7 @@ namespace Core {
             return formatBuffer(s, index, Core::forward<Args>(args)...);
         }
 
-        check_return Error formatBuffer(ArrayString& s, int index) {
+        check_return Error formatBuffer(Char32String& s, int index) {
             while(index < length) {
                 CORE_RETURN_ERROR(s.append(data[index++]));
             }
@@ -481,20 +579,28 @@ namespace Core {
     }
 
     template<int N>
-    using String8 = ArrayString<N, char>;
+    using String8 = ArrayCharString<N>;
 
     template<int N>
-    using String32 = ArrayString<N, char32_t>;
+    using String32 = Char32String<N>;
+}
+
+template<int N>
+bool operator==(const c32* cs, const Core::Char32String<N>& s) {
+    return s == cs;
+}
+
+template<int N>
+bool operator!=(const c32* cs, const Core::Char32String<N>& s) {
+    return s != cs;
 }
 
-template<int N, typename CharType>
-bool operator==(const CharType* cs, const Core::ArrayString<N, CharType>& s) {
+inline bool operator==(const char* cs, const Core::CharString& s) {
     return s == cs;
 }
 
-template<int N, typename CharType>
-bool operator!=(const CharType* cs, const Core::ArrayString<N, CharType>& s) {
+inline bool operator!=(const char* cs, const Core::CharString& s) {
     return s != cs;
 }
 
-#endif
+#endif

+ 306 - 0
src/ArrayString.cpp

@@ -0,0 +1,306 @@
+#include "core/utils/ArrayString.hpp"
+
+#include "core/utils/Error.hpp"
+
+using CharString = Core::CharString;
+using Error = Core::Error;
+
+static c32 read(const char*& s) {
+    if(*s == '\0') {
+        return 0;
+    }
+    return static_cast<c32>(*(s++));
+}
+
+Error Core::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;
+}
+
+template<unsigned int L>
+void unicodeToChar(c32 c, char (&buffer)[L]) {
+    static_assert(L >= 5, "to small char buffer");
+    buffer[0] = '\0';
+    if(c < (1 << 7)) {
+        buffer[0] = static_cast<char>(((c >> 0) & 0x7F) | 0x0);
+        buffer[1] = '\0';
+    } else if(c < (1 << 11)) {
+        buffer[0] = static_cast<char>(((c >> 6) & 0x1F) | 0xC0);
+        buffer[1] = static_cast<char>(((c >> 0) & 0x3F) | 0x80);
+        buffer[2] = '\0';
+    } else if(c < (1 << 16)) {
+        buffer[0] = static_cast<char>(((c >> 12) & 0x0F) | 0xE0);
+        buffer[1] = static_cast<char>(((c >> 6) & 0x3F) | 0x80);
+        buffer[2] = static_cast<char>(((c >> 0) & 0x3F) | 0x80);
+        buffer[3] = '\0';
+    } else if(c < (1 << 21)) {
+        buffer[0] = static_cast<char>(((c >> 18) & 0x07) | 0xF0);
+        buffer[1] = static_cast<char>(((c >> 12) & 0x3F) | 0x80);
+        buffer[2] = static_cast<char>(((c >> 6) & 0x3F) | 0x80);
+        buffer[3] = static_cast<char>(((c >> 0) & 0x3F) | 0x80);
+        buffer[4] = '\0';
+    }
+}
+
+CharString::CharString(char* buffer, i32 bufferSize)
+    : length(0), capacity(bufferSize), hash(0), data(buffer) {
+    data[0] = '\0';
+}
+
+Error CharString::copyFrom(const CharString& s) {
+    clear();
+    return s.toString(*this);
+}
+
+bool CharString::operator==(const char* s) const {
+    const char* p = data;
+    while(*s == *p && *s != '\0') {
+        s++;
+        p++;
+    }
+    return *s == *p;
+}
+
+bool CharString::operator==(const CharString& 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 CharString::operator!=(const char* s) const {
+    return !((*this) == s);
+}
+
+bool CharString::operator!=(const CharString& other) const {
+    return !((*this) == other);
+}
+
+char CharString::operator[](int index) const {
+    return data[index];
+}
+
+int CharString::getLength() const {
+    return length;
+}
+
+int CharString::getCapacity() const {
+    return capacity - 1;
+}
+
+Error CharString::append(char c) {
+    if(length >= capacity - 1) {
+        return Error::CAPACITY_REACHED;
+    }
+    data[length++] = c;
+    data[length] = '\0';
+    addToHash(static_cast<c32>(c));
+    return Error::NONE;
+}
+
+Error CharString::append(signed char c) {
+    return append(static_cast<char>(c));
+}
+
+Error CharString::append(unsigned char c) {
+    return append(static_cast<char>(c));
+}
+
+Error CharString::append(wchar_t c) {
+    return append(static_cast<c32>(c));
+}
+
+Error CharString::append(c32 c) {
+    char buffer[5];
+    unicodeToChar(c, buffer);
+    return append(static_cast<const char*>(buffer));
+}
+
+Error CharString::append(const char* 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;
+}
+
+Error CharString::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;
+}
+
+Error CharString::append(const signed char* s) {
+    return append(reinterpret_cast<const char*>(s));
+}
+
+Error CharString::append(const unsigned char* s) {
+    return append(reinterpret_cast<const char*>(s));
+}
+
+Error CharString::append(bool b) {
+    return b ? append("true") : append("false");
+}
+
+Error CharString::append(Error e) {
+    return append(getErrorName(e));
+}
+
+Error CharString::toString(CharString& s) const {
+    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 CharString::clear() {
+    length = 0;
+    hash = 0;
+    data[0] = '\0';
+}
+
+u32 CharString::hashCode() const {
+    return hash;
+}
+
+Error CharString::print() const {
+    for(int i = 0; i < length; i++) {
+        CORE_RETURN_ERROR(Core::putChar(data[i]));
+    }
+    return Error::NONE;
+}
+
+Error CharString::printLine() const {
+    CORE_RETURN_ERROR(print());
+    CORE_RETURN_ERROR(Core::putChar('\n'));
+    return Error::NONE;
+}
+
+bool CharString::startsWidth(const CharString& other, int from) const {
+    if(from > length - other.getLength()) {
+        return false;
+    }
+    for(int i = 0; i < other.getLength(); i++) {
+        if(data[from + i] != other[i]) {
+            return false;
+        }
+    }
+    return true;
+}
+
+int CharString::search(const CharString& other, int from) const {
+    for(int i = from; i < length; i++) {
+        if(startsWidth(other, i)) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+bool CharString::contains(const CharString& other, int from) const {
+    return search(other, from) >= 0;
+}
+
+int CharString::search(char u, int from) const {
+    for(int i = from; i < length; i++) {
+        if(data[i] == u) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+bool CharString::contains(char u, int from) const {
+    return search(u, from) >= 0;
+}
+
+Error CharString::substring(CharString& s, int from, int to) const {
+    s.clear();
+    from = Math::max(from, 0);
+    to = Math::min(to, length - 1);
+    for(int i = from; i <= to; i++) {
+        CORE_RETURN_ERROR(s.append(data[i]));
+    }
+    return Error::NONE;
+}
+
+Error CharString::substring(CharString& s, int from) const {
+    return substring(s, from, length - 1);
+}
+
+Error CharString::replace(CharString& s, const CharString& search,
+                          const CharString& replace) {
+    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++;
+        }
+    }
+    return copyFrom(s);
+}
+
+void CharString::replace(char search, char replace) {
+    hash = 0;
+    for(int i = 0; i < length; i++) {
+        if(data[i] == search) {
+            data[i] = replace;
+        }
+        addToHash(static_cast<c32>(data[i]));
+    }
+}
+
+CharString::operator const char*() const {
+    return data;
+}
+
+void CharString::addToHash(c32 u) {
+    hash = static_cast<u32>(2120251889) * hash + static_cast<u32>(u);
+}
+
+Error CharString::formatBuffer(CharString& s, int index) {
+    while(index < length) {
+        CORE_RETURN_ERROR(s.append(data[index++]));
+    }
+    return Error::NONE;
+}

+ 28 - 19
test/modules/ArrayStringTests.cpp

@@ -2,8 +2,7 @@
 #include "core/data/HashMap.hpp"
 #include "core/utils/ArrayString.hpp"
 
-template class Core::ArrayString<128, char>;
-template class Core::ArrayString<128, char32_t>;
+template class Core::Char32String<128>;
 
 using String8 = Core::String8<128>;
 using String32 = Core::String32<128>;
@@ -51,7 +50,7 @@ static void testStringAppend8() {
 }
 
 static void testStringAppendOverflow8() {
-    Core::ArrayString<6, char> s;
+    Core::ArrayCharString<6> s;
     CORE_TEST_ERROR(s.append("te"));
     CORE_TEST_EQUAL(Core::Error::CAPACITY_REACHED, s.append("23334444"));
     CORE_TEST_TRUE(build("te23334444") != s);
@@ -193,7 +192,7 @@ static void testBool8() {
 }
 
 static void testIntOverflow8() {
-    Core::ArrayString<4, char> s;
+    Core::ArrayCharString<4> s;
     CORE_TEST_EQUAL(Core::Error::CAPACITY_REACHED, s.append(123456));
 
     String8 o;
@@ -377,18 +376,28 @@ static void testSubString8() {
     String8 s;
     CORE_TEST_ERROR(s.append("01üää3ä"));
 
-    CORE_TEST_STRING("01üää3ä", s.substring(-2));
-    CORE_TEST_STRING("1üää3ä", s.substring(1));
-    CORE_TEST_STRING("üää3ä", s.substring(2));
-    CORE_TEST_STRING("ää3ä", s.substring(4));
-    CORE_TEST_STRING("ä3ä", s.substring(6));
-
-    CORE_TEST_STRING("01üää3ä", s.substring(0, 10));
-    CORE_TEST_STRING("1üää3", s.substring(1, 8));
-    CORE_TEST_STRING("üää", s.substring(2, 7));
-    CORE_TEST_STRING("ä", s.substring(4, 5));
-    CORE_TEST_STRING("", s.substring(4, 2));
-    CORE_TEST_STRING("ä3ä", s.substring(6, 23));
+    String8 sub;
+    CORE_TEST_ERROR(s.substring(sub, -2));
+    CORE_TEST_STRING("01üää3ä", sub);
+    CORE_TEST_ERROR(s.substring(sub, 2));
+    CORE_TEST_STRING("üää3ä", sub);
+    CORE_TEST_ERROR(s.substring(sub, 4));
+    CORE_TEST_STRING("ää3ä", sub);
+    CORE_TEST_ERROR(s.substring(sub, 6));
+    CORE_TEST_STRING("ä3ä", sub);
+
+    CORE_TEST_ERROR(s.substring(sub, 0, 10));
+    CORE_TEST_STRING("01üää3ä", sub);
+    CORE_TEST_ERROR(s.substring(sub, 1, 8));
+    CORE_TEST_STRING("1üää3", sub);
+    CORE_TEST_ERROR(s.substring(sub, 2, 7));
+    CORE_TEST_STRING("üää", sub);
+    CORE_TEST_ERROR(s.substring(sub, 4, 5));
+    CORE_TEST_STRING("ä", sub);
+    CORE_TEST_ERROR(s.substring(sub, 4, 2));
+    CORE_TEST_STRING("", sub);
+    CORE_TEST_ERROR(s.substring(sub, 6, 23));
+    CORE_TEST_STRING("ä3ä", sub);
 }
 
 static void testReplace8() {
@@ -523,7 +532,7 @@ static void testStringAppend32() {
 }
 
 static void testStringAppendOverflow32() {
-    Core::ArrayString<6, c32> s;
+    Core::Char32String<6> s;
     CORE_TEST_ERROR(s.append(U"te"));
     CORE_TEST_EQUAL(Core::Error::CAPACITY_REACHED, s.append(U"23334444"));
     CORE_TEST_TRUE(build(U"te23334444") != s);
@@ -665,7 +674,7 @@ static void testBool32() {
 }
 
 static void testIntOverflow32() {
-    Core::ArrayString<4, c32> s;
+    Core::Char32String<4> s;
     CORE_TEST_EQUAL(Core::Error::CAPACITY_REACHED, s.append(123456));
 
     String32 o;
@@ -1086,4 +1095,4 @@ void Core::testArrayString() {
     testKeepHash32();
 
     testConversion();
-}
+}