Browse Source

String is now checked

Kajetan Johannes Hammerle 1 year ago
parent
commit
fdf139399d

+ 13 - 8
data/Array.h

@@ -1,7 +1,7 @@
 #ifndef CORE_ARRAY_H
 #define CORE_ARRAY_H
 
-#include "utils/String.h"
+#include "utils/ArrayString.h"
 
 namespace Core {
     template<typename T, int N>
@@ -50,16 +50,21 @@ namespace Core {
             return N;
         }
 
-        void toString(String& s) const {
-            s.append("[");
+        // returns true on error
+        template<int L>
+        check_return bool toString(ArrayString<L>& s) const {
+            if(s.append("[")) {
+                return true;
+            }
             for(int i = 0; i < N - 1; i++) {
-                s.append(data[i]);
-                s.append(", ");
+                if(s.append(data[i]) || s.append(", ")) {
+                    return true;
+                }
             }
-            if(N > 0) {
-                s.append(data[N - 1]);
+            if(N > 0 && s.append(data[N - 1])) {
+                return true;
             }
-            s.append("]");
+            return s.append("]");
         }
     };
 }

+ 15 - 11
data/ArrayList.h

@@ -1,8 +1,7 @@
-#ifndef ARRAYLIST_H
-#define ARRAYLIST_H
+#ifndef CORE_ARRAYLIST_H
+#define CORE_ARRAYLIST_H
 
-#include "utils/String.h"
-#include "utils/Utility.h"
+#include "utils/ArrayString.h"
 
 namespace Core {
     template<typename T, int N>
@@ -108,16 +107,21 @@ namespace Core {
             return false;
         }
 
-        void toString(String& s) const {
-            s.append("[");
+        // returns true on error
+        template<int L>
+        check_return bool toString(ArrayString<L>& s) const {
+            if(s.append("[")) {
+                return true;
+            }
             for(int i = 0; i < length - 1; i++) {
-                s.append(begin()[i]);
-                s.append(", ");
+                if(s.append(begin()[i]) || s.append(", ")) {
+                    return true;
+                }
             }
-            if(length > 0) {
-                s.append(begin()[length - 1]);
+            if(length > 0 && s.append(begin()[length - 1])) {
+                return true;
             }
-            s.append("]");
+            return s.append("]");
         }
 
     private:

+ 0 - 12
data/BitArray.cpp

@@ -159,18 +159,6 @@ check_return bool Core::BitArray::resize(int newLength, int newBits) {
     return false;
 }
 
-void Core::BitArray::toString(String& s) const {
-    s.append("[");
-    for(int i = 0; i < length - 1; i++) {
-        s.append(get(i));
-        s.append(", ");
-    }
-    if(length > 0) {
-        s.append(get(length - 1));
-    }
-    s.append("]");
-}
-
 void Core::BitArray::swap(BitArray& other) {
     Core::swap(length, other.length);
     Core::swap(bits, other.bits);

+ 17 - 3
data/BitArray.h

@@ -1,8 +1,7 @@
 #ifndef CORE_BIT_ARRAY_H
 #define CORE_BIT_ARRAY_H
 
-#include "utils/Check.h"
-#include "utils/String.h"
+#include "utils/ArrayString.h"
 
 namespace Core {
     class BitArray final {
@@ -35,7 +34,22 @@ namespace Core {
 
         void fill(int value);
 
-        void toString(String& s) const;
+        // returns true on error
+        template<int L>
+        check_return bool toString(ArrayString<L>& s) const {
+            if(s.append("[")) {
+                return true;
+            }
+            for(int i = 0; i < length - 1; i++) {
+                if(s.append(get(i)) || s.append(", ")) {
+                    return true;
+                }
+            }
+            if(length > 0 && s.append(get(length - 1))) {
+                return true;
+            }
+            return s.append("]");
+        }
 
     private:
         void swap(BitArray& other);

+ 13 - 11
data/HashMap.h

@@ -1,13 +1,9 @@
 #ifndef CORE_HASHMAP_H
 #define CORE_HASHMAP_H
 
-#include <stdio.h>
-
 #include "data/LinkedList.h"
 #include "data/List.h"
-#include "math/Math.h"
-#include "utils/String.h"
-#include "utils/Types.h"
+#include "utils/ArrayString.h"
 
 namespace Core {
     template<typename K, typename V>
@@ -264,19 +260,25 @@ namespace Core {
             return ConstEntryIterator(nodes.end());
         }
 
-        void toString(String& s) const {
-            s.append("[");
+        // returns true on error
+        template<int L>
+        check_return bool toString(ArrayString<L>& s) const {
+            if(s.append("[")) {
+                return true;
+            }
             bool c = false;
             for(const NodePointerList& list : nodePointers) {
                 for(const NodePointer& n : list) {
-                    if(c) {
-                        s.append(", ");
+                    if(c && s.append(", ")) {
+                        return true;
+                    } else if(s.append(n->data.key) || s.append(" = ") ||
+                              s.append(n->data.value)) {
+                        return true;
                     }
-                    s.append(n->data.key).append(" = ").append(n->data.value);
                     c = true;
                 }
             }
-            s.append("]");
+            return s.append("]");
         }
 
     private:

+ 13 - 12
data/LinkedList.h

@@ -1,11 +1,7 @@
 #ifndef CORE_LINKED_LIST_H
 #define CORE_LINKED_LIST_H
 
-#include <stdio.h>
-
-#include "utils/Check.h"
-#include "utils/String.h"
-#include "utils/Utility.h"
+#include "utils/ArrayString.h"
 
 namespace Core {
     template<typename T>
@@ -137,18 +133,23 @@ namespace Core {
             last = nullptr;
         }
 
-        void toString(String& s) const {
-            s.append("[");
+        // returns true on error
+        template<int L>
+        check_return bool toString(ArrayString<L>& s) const {
+            if(s.append("[")) {
+                return true;
+            }
             auto iter = begin();
             for(int i = 0; i < length - 1; i++) {
-                s.append(*iter);
-                s.append(", ");
+                if(s.append(*iter) || s.append(", ")) {
+                    return true;
+                }
                 ++iter;
             }
-            if(length > 0) {
-                s.append(*iter);
+            if(length > 0 && s.append(*iter)) {
+                return true;
             }
-            s.append("]");
+            return s.append("]");
         }
 
         void remove(Node*& n) {

+ 13 - 10
data/List.h

@@ -1,9 +1,7 @@
 #ifndef CORE_LIST_H
 #define CORE_LIST_H
 
-#include "math/Math.h"
-#include "utils/String.h"
-#include "utils/Utility.h"
+#include "utils/ArrayString.h"
 
 namespace Core {
     template<typename T>
@@ -196,16 +194,21 @@ namespace Core {
             return false;
         }
 
-        void toString(String& s) const {
-            s.append("[");
+        // returns true on error
+        template<int L>
+        check_return bool toString(ArrayString<L>& s) const {
+            if(s.append("[")) {
+                return true;
+            }
             for(int i = 0; i < length - 1; i++) {
-                s.append(data[i]);
-                s.append(", ");
+                if(s.append(data[i]) || s.append(", ")) {
+                    return true;
+                }
             }
-            if(length > 0) {
-                s.append(data[length - 1]);
+            if(length > 0 && s.append(data[length - 1])) {
+                return true;
             }
-            s.append("]");
+            return s.append("]");
         }
 
     private:

+ 8 - 6
math/Math.h

@@ -3,6 +3,8 @@
 
 #include <math.h>
 
+#include "utils/Utility.h"
+
 #ifndef M_PI
 #define M_PI 3.14159265358979323846
 #endif
@@ -29,24 +31,24 @@ namespace Core::Math {
     }
 
     template<typename T>
-    const T& min(const T& t) {
+    constexpr const T& min(const T& t) {
         return t;
     }
 
     template<typename T, typename... Args>
-    const T& min(const T& t, Args&&... args) {
-        const T& o = min(args...);
+    constexpr const T& min(const T& t, Args&&... args) {
+        const T& o = min(Core::forward<Args>(args)...);
         return t < o ? t : o;
     }
 
     template<typename T>
-    const T& max(const T& t) {
+    constexpr const T& max(const T& t) {
         return t;
     }
 
     template<typename T, typename... Args>
-    const T& max(const T& t, Args&&... args) {
-        const T& o = max(args...);
+    constexpr const T& max(const T& t, Args&&... args) {
+        const T& o = max(Core::forward<Args>(args)...);
         return o < t ? t : o;
     }
 

+ 1 - 2
meson.build

@@ -1,7 +1,6 @@
 project('core', 'cpp', default_options : ['cpp_std=c++2a'])
 
 src = [
-    'utils/String.cpp',
     'utils/Logger.cpp',
     'utils/Utility.cpp',
     'data/BitArray.cpp',
@@ -11,7 +10,7 @@ src_tests = [
     'test/Main.cpp',
     'test/Test.cpp',
     'tests/ArrayTests.cpp',
-    'tests/StringTests.cpp',
+    'tests/ArrayStringTests.cpp',
     'tests/UtilityTests.cpp',
     'tests/ArrayListTests.cpp',
     'tests/BitArrayTests.cpp',

+ 20 - 7
test/Main.cpp

@@ -1,24 +1,37 @@
+#include <stdio.h>
+
 #include "tests/ArrayListTests.h"
+#include "tests/ArrayStringTests.h"
 #include "tests/ArrayTests.h"
 #include "tests/BitArrayTests.h"
 #include "tests/HashMapTests.h"
 #include "tests/LinkedListTests.h"
 #include "tests/ListTests.h"
 #include "tests/MathTests.h"
-#include "tests/StringTests.h"
 #include "tests/UniquePointerTests.h"
 #include "tests/UtilityTests.h"
+#include "utils/Utility.h"
+
+static void onExit(int code, void* data) {
+    unsigned int i = *static_cast<unsigned int*>(data);
+    printf("Hello from exit %d: 0x%x\n", code, i);
+}
 
 int main() {
-    Core::ArrayTests::test();
-    Core::StringTests::test();
-    Core::UtilityTests::test();
     Core::ArrayListTests::test();
+    Core::ArrayStringTests::test();
+    Core::ArrayTests::test();
     Core::BitArrayTests::test();
-    Core::MathTests::test();
-    Core::ListTests::test();
-    Core::LinkedListTests::test();
     Core::HashMapTests::test();
+    Core::LinkedListTests::test();
+    Core::ListTests::test();
+    Core::MathTests::test();
     Core::UniquePointerTests::test();
+    Core::UtilityTests::test();
+
+    unsigned int data = 0x1234;
+    Core::setExitHandler(onExit, &data);
+    Core::exitWithHandler(0);
+
     return 0;
 }

+ 1 - 1
test/Test.cpp

@@ -1,6 +1,6 @@
 #include "test/Test.h"
 
-Core::Test::Test(const String& name_) : tests(0), successTests(0), name(name_) {
+Core::Test::Test(const char* name_) : tests(0), successTests(0), name(name_) {
 }
 
 void Core::Test::finalize() {

+ 2 - 2
test/Test.h

@@ -7,10 +7,10 @@ namespace Core {
     class Test final {
         int tests;
         int successTests;
-        String name;
+        const char* name;
 
     public:
-        Test(const String& name);
+        Test(const char* name);
         void finalize();
 
         template<typename T>

+ 11 - 3
tests/ArrayListTests.cpp

@@ -4,6 +4,14 @@
 #include "test/Test.h"
 
 using IntList = Core::ArrayList<int, 20>;
+using String = Core::ArrayString<128>;
+
+template<typename T>
+static String build(Core::Test& test, const T& t) {
+    String s;
+    test.checkFalse(s.append(t), "append works");
+    return s;
+}
 
 static void testAdd(Core::Test& test) {
     IntList list;
@@ -116,19 +124,19 @@ static void testToString1(Core::Test& test) {
     test.checkFalse(list.add(1), "add works 20");
     test.checkFalse(list.add(243), "add works 21");
     test.checkFalse(list.add(-423), "add works 22");
-    test.checkEqual(Core::String("[1, 243, -423]"), Core::String(list),
+    test.checkEqual(build(test, "[1, 243, -423]"), build(test, list),
                     "to string 1");
 }
 
 static void testToString2(Core::Test& test) {
     IntList list;
     test.checkFalse(list.add(1), "add works 23");
-    test.checkEqual(Core::String("[1]"), Core::String(list), "to string 2");
+    test.checkEqual(build(test, "[1]"), build(test, list), "to string 2");
 }
 
 static void testToString3(Core::Test& test) {
     IntList list;
-    test.checkEqual(Core::String("[]"), Core::String(list), "to string 3");
+    test.checkEqual(build(test, "[]"), build(test, list), "to string 3");
 }
 
 static void testRemove(Core::Test& test) {

+ 306 - 0
tests/ArrayStringTests.cpp

@@ -0,0 +1,306 @@
+#include "tests/ArrayStringTests.h"
+
+#include "data/HashMap.h"
+#include "test/Test.h"
+#include "utils/ArrayString.h"
+
+using String = Core::ArrayString<128>;
+
+static String build(Core::Test& test, const char* cs) {
+    String s;
+    test.checkFalse(s.append(cs), "append works");
+    return s;
+}
+
+static void testEquality(Core::Test& test) {
+    String s = build(test, "test");
+    test.checkTrue(s == "test", "equality with c-string");
+    test.checkTrue(s == build(test, "test"), "equality with another string");
+    test.checkTrue("test" == s, "inverse equality with c-string");
+    test.checkTrue(build(test, "test") == s,
+                   "inverse equality with another string");
+    test.checkTrue(s == s, "equality with itself");
+}
+
+static void testUnicodeEquality(Core::Test& test) {
+    const char* cs = "\u0040\u0400\u8000\U00100000";
+    String s = build(test, cs);
+    test.checkTrue(s == cs, "unicode equality with c-string");
+    test.checkTrue(s == build(test, cs),
+                   "unicode equality with another string");
+    test.checkTrue(cs == s, "unicode inverse equality with c-string");
+    test.checkTrue(build(test, cs) == s,
+                   "unicode inverse equality with another string");
+    test.checkTrue(s == s, "unicode equality with itself");
+}
+
+static void testInequality(Core::Test& test) {
+    String s = build(test, "test");
+    test.checkFalse(s != "test", "inequality with c-string");
+    test.checkFalse(s != build(test, "test"), "inequality with another string");
+    test.checkFalse("test" != s, "inverse inequality with c-string");
+    test.checkFalse(build(test, "test") != s,
+                    "inverse inequality with another string");
+    test.checkFalse(s != s, "inequality with itself");
+}
+
+static void testStringAppend(Core::Test& test) {
+    String s = build(test, "test");
+    test.checkFalse(s.append("22"), "multiple append works");
+    test.checkFalse(s.append("333"), "multiple append works");
+    test.checkFalse(s.append("4444"), "multiple append works");
+    test.checkEqual(build(test, "test223334444"), s, "multiple appends");
+}
+
+static void testStringAppendOverflow(Core::Test& test) {
+    Core::ArrayString<20> s;
+    test.checkFalse(s.append("te"), "overflow test 1");
+    test.checkTrue(s.append("23334444"), "overflow test 2");
+    test.checkTrue(build(test, "te23334444") != s, "overflow test 3");
+}
+
+static void testCharacters(Core::Test& test) {
+    String s = build(test, "test");
+    test.checkEqual<u32>('t', s[0], "character read 1");
+    test.checkEqual<u32>('e', s[1], "character read 2");
+    test.checkEqual<u32>('s', s[2], "character read 3");
+    test.checkEqual<u32>('t', s[3], "character read 4");
+}
+
+static void testLength(Core::Test& test) {
+    String s = build(test, "test");
+    test.checkEqual(4, s.getLength(), "length 1");
+    test.checkFalse(s.append("aaa"), "length append works");
+    test.checkEqual(7, s.getLength(), "length 2");
+}
+
+static void testChar(Core::Test& test) {
+    String s = build(test, "test");
+    for(char i = 'a'; i < 'd'; i++) {
+        test.checkFalse(s.append(i), "char append");
+    }
+    test.checkEqual(build(test, "testabc"), s, "char append");
+}
+
+static void testSignedChar(Core::Test& test) {
+    String s = build(test, "test");
+    for(signed char i = 'b'; i < 'e'; i++) {
+        test.checkFalse(s.append(i), "signed char append");
+    }
+    test.checkEqual(build(test, "testbcd"), s, "signed char append");
+}
+
+static void testUnsignedChar(Core::Test& test) {
+    String s = build(test, "test");
+    for(unsigned char i = 'c'; i < 'f'; i++) {
+        test.checkFalse(s.append(i), "unsigned char append");
+    }
+    test.checkEqual(build(test, "testcde"), s, "unsigned char append");
+}
+
+static void testSignedShort(Core::Test& test) {
+    String s = build(test, "test");
+    for(signed short i = 100; i < 103; i++) {
+        test.checkFalse(s.append(i), "signed short append");
+    }
+    test.checkEqual(build(test, "test100101102"), s, "signed short append");
+}
+
+static void testUnsignedShort(Core::Test& test) {
+    String s = build(test, "test");
+    for(unsigned short i = 101; i < 104; i++) {
+        test.checkFalse(s.append(i), "unsigned short append");
+    }
+    test.checkEqual(build(test, "test101102103"), s, "unsigned short append");
+}
+
+static void testSignedInt(Core::Test& test) {
+    String s = build(test, "test");
+    for(signed int i = 102; i < 105; i++) {
+        test.checkFalse(s.append(i), "signed int append");
+    }
+    test.checkEqual(build(test, "test102103104"), s, "signed int append");
+}
+
+static void testUnsignedInt(Core::Test& test) {
+    String s = build(test, "test");
+    for(unsigned int i = 103; i < 106; i++) {
+        test.checkFalse(s.append(i), "unsigned int append");
+    }
+    test.checkEqual(build(test, "test103104105"), s, "unsigned int append");
+}
+
+static void testSignedLong(Core::Test& test) {
+    String s = build(test, "test");
+    for(signed long i = 104; i < 107; i++) {
+        test.checkFalse(s.append(i), "signed long append");
+    }
+    test.checkEqual(build(test, "test104105106"), s, "signed long append");
+}
+
+static void testUnsignedLong(Core::Test& test) {
+    String s = build(test, "test");
+    for(unsigned long i = 105; i < 108; i++) {
+        test.checkFalse(s.append(i), "unsigned long append");
+    }
+    test.checkEqual(build(test, "test105106107"), s, "unsigned long append");
+}
+
+static void testSignedLongLong(Core::Test& test) {
+    String s = build(test, "test");
+    for(signed long long i = 106; i < 109; i++) {
+        test.checkFalse(s.append(i), "signed long long append");
+    }
+    test.checkEqual(build(test, "test106107108"), s, "signed long long append");
+}
+
+static void testUnsignedLongLong(Core::Test& test) {
+    String s = build(test, "test");
+    for(unsigned long long i = 107; i < 110; i++) {
+        test.checkFalse(s.append(i), "unsigned long long append");
+    }
+    test.checkEqual(build(test, "test107108109"), s,
+                    "unsigned long long append");
+}
+
+static void testFloat(Core::Test& test) {
+    String s = build(test, "test");
+    for(float i = 108; i < 111; i++) {
+        test.checkFalse(s.append(i), "float append");
+    }
+    test.checkEqual(build(test, "test108.00109.00110.00"), s, "float append");
+}
+
+static void testDouble(Core::Test& test) {
+    String s = build(test, "test");
+    for(double i = 109; i < 112; i++) {
+        test.checkFalse(s.append(i), "double append");
+    }
+    test.checkEqual(build(test, "test109.00110.00111.00"), s, "double append");
+}
+
+static void testLongDouble(Core::Test& test) {
+    String s = build(test, "test");
+    for(long double i = 110; i < 113; i++) {
+        test.checkFalse(s.append(i), "long double append");
+    }
+    test.checkEqual(build(test, "test110.00111.00112.00"), s,
+                    "long double append");
+}
+
+static void testBool(Core::Test& test) {
+    String s = build(test, "test");
+    test.checkFalse(s.append(true), "bool append 1");
+    test.checkFalse(s.append(false), "bool append 2");
+    test.checkFalse(s.append(true), "bool append 3");
+    test.checkEqual(build(test, "testtruefalsetrue"), s, "bool append 4");
+}
+
+static void testIntOverflow(Core::Test& test) {
+    Core::ArrayString<20> s;
+    test.checkTrue(s.append(123456), "int overflow");
+
+    String o;
+    for(int i = 0; i < s.getCapacity(); i++) {
+        test.checkFalse(o.append(i + 1), "int overflow compare build");
+    }
+
+    test.checkTrue(o == s, "int overflow compare");
+}
+
+static void testUnicode(Core::Test& test) {
+    String s;
+    test.checkFalse(s.appendUnicode('\u0040'), "unicode 1");
+    test.checkFalse(s.appendUnicode(L'\u0400'), "unicode 2");
+    test.checkFalse(s.appendUnicode(L'\u8000'), "unicode 3");
+    test.checkFalse(s.appendUnicode(U'\U00100000'), "unicode 4");
+    test.checkEqual(build(test, "\u0040\u0400\u8000\U00100000"), s,
+                    "unicode append");
+}
+
+static void testClear(Core::Test& test) {
+    String s = build(test, "test");
+    test.checkFalse(s.append(1234), "clear 1");
+    s.clear();
+    test.checkFalse(s.append("wusi"), "clear 2");
+    test.checkFalse(s.append("1234"), "clear 3");
+    test.checkEqual(build(test, "wusi1234"), s, "clear 4");
+}
+
+static void testHashCode(Core::Test& test) {
+    String s;
+    test.checkFalse(s.append("a"), "hash code 1");
+    test.checkFalse(s.append("bc"), "hash code 2");
+    test.checkFalse(s.append(20), "hash code 3");
+    test.checkFalse(s.append(25.5f), "hash code 4");
+    test.checkFalse(s.append(true), "hash code 5");
+    test.checkEqual(build(test, "abc2025.50true").hashCode(), s.hashCode(),
+                    "string modification recalculates hash 1");
+    s.clear();
+    test.checkEqual(String().hashCode(), s.hashCode(),
+                    "string modification recalculates hash 2");
+}
+
+static void testAddSelf(Core::Test& test) {
+    String s;
+    test.checkFalse(s.append("test1"), "add self 1");
+    test.checkFalse(s.append(s), "add self 2");
+    test.checkFalse(s.append(s), "add self 3");
+    test.checkEqual(build(test, "test1test1test1test1"), s, "add self 4");
+}
+
+static void testAsHashMapKey(Core::Test& test) {
+    Core::HashMap<String, int> map;
+    test.checkFalse(map.add(build(test, "wusi"), 3), "as hashmap key 1");
+    test.checkFalse(map.add(build(test, "hiThere"), 7), "as hashmap key 2");
+    test.checkFalse(map.add(build(test, "baum123"), 5), "as hashmap key 3");
+
+    int* a = map.search(build(test, "wusi"));
+    int* b = map.search(build(test, "hiThere"));
+    int* c = map.search(build(test, "baum123"));
+    int* d = map.search(build(test, "423hifd"));
+
+    test.checkEqual(true, a != nullptr, "strings works as hash key 1");
+    test.checkEqual(true, b != nullptr, "strings works as hash key 2");
+    test.checkEqual(true, c != nullptr, "strings works as hash key 3");
+    test.checkEqual(true, d == nullptr, "strings works as hash key 4");
+
+    if(a != nullptr && b != nullptr && c != nullptr) {
+        test.checkEqual(3, *a, "strings works as hash key 1");
+        test.checkEqual(7, *b, "strings works as hash key 2");
+        test.checkEqual(5, *c, "strings works as hash key 3");
+    }
+}
+
+void Core::ArrayStringTests::test() {
+    Core::Test test("String");
+    testEquality(test);
+    testUnicodeEquality(test);
+    testInequality(test);
+    testStringAppend(test);
+    testStringAppendOverflow(test);
+    testCharacters(test);
+    testLength(test);
+    testChar(test);
+    testSignedChar(test);
+    testUnsignedChar(test);
+    testSignedShort(test);
+    testUnsignedShort(test);
+    testSignedInt(test);
+    testUnsignedInt(test);
+    testSignedLong(test);
+    testUnsignedLong(test);
+    testSignedLongLong(test);
+    testUnsignedLongLong(test);
+    testFloat(test);
+    testDouble(test);
+    testLongDouble(test);
+    testBool(test);
+    testIntOverflow(test);
+    testUnicode(test);
+    testClear(test);
+    testHashCode(test);
+    testAddSelf(test);
+    testAsHashMapKey(test);
+    test.finalize();
+}

+ 8 - 0
tests/ArrayStringTests.h

@@ -0,0 +1,8 @@
+#ifndef CORE_ARRAY_STRING_TESTS_H
+#define CORE_ARRAY_STRING_TESTS_H
+
+namespace Core::ArrayStringTests {
+    void test();
+}
+
+#endif

+ 11 - 2
tests/ArrayTests.cpp

@@ -3,19 +3,28 @@
 #include "data/Array.h"
 #include "test/Test.h"
 
+using String = Core::ArrayString<128>;
+
+template<typename T>
+static String build(Core::Test& test, const T& t) {
+    String s;
+    test.checkFalse(s.append(t), "append works");
+    return s;
+}
+
 static void testToString1(Core::Test& test) {
     Core::Array<int, 3> a;
     a[0] = 1;
     a[1] = 243;
     a[2] = -423;
-    test.checkEqual(Core::String("[1, 243, -423]"), Core::String(a),
+    test.checkEqual(build(test, "[1, 243, -423]"), build(test, a),
                     "to string 1");
 }
 
 static void testToString2(Core::Test& test) {
     Core::Array<int, 1> a;
     a[0] = 1;
-    test.checkEqual(Core::String("[1]"), Core::String(a), "to string 2");
+    test.checkEqual(build(test, "[1]"), build(test, a), "to string 2");
 }
 
 void Core::ArrayTests::test() {

+ 12 - 3
tests/BitArrayTests.cpp

@@ -3,6 +3,15 @@
 #include "data/BitArray.h"
 #include "test/Test.h"
 
+using String = Core::ArrayString<128>;
+
+template<typename T>
+static String build(Core::Test& test, const T& t) {
+    String s;
+    test.checkFalse(s.append(t), "append works");
+    return s;
+}
+
 static void testSetRead(Core::Test& test) {
     Core::BitArray bits;
     test.checkFalse(bits.resize(4, 3), "resize works 1");
@@ -97,7 +106,7 @@ static void testToString1(Core::Test& test) {
     Core::BitArray bits;
     test.checkFalse(bits.resize(4, 3), "resize works 7");
     bits.set(0, 1).set(1, 2).set(2, 3).set(3, 4);
-    test.checkEqual(Core::String("[1, 2, 3, 4]"), Core::String(bits),
+    test.checkEqual(build(test, "[1, 2, 3, 4]"), build(test, bits),
                     "bit array to string 1");
 }
 
@@ -105,13 +114,13 @@ static void testToString2(Core::Test& test) {
     Core::BitArray bits;
     test.checkFalse(bits.resize(1, 3), "resize works 8");
     bits.set(0, 1);
-    test.checkEqual(Core::String("[1]"), Core::String(bits),
+    test.checkEqual(build(test, "[1]"), build(test, bits),
                     "bit array to string 2");
 }
 
 static void testToString3(Core::Test& test) {
     Core::BitArray bits;
-    test.checkEqual(Core::String("[]"), Core::String(bits),
+    test.checkEqual(build(test, "[]"), build(test, bits),
                     "bit array to string 3");
 }
 

+ 15 - 5
tests/HashMapTests.cpp

@@ -4,6 +4,14 @@
 #include "test/Test.h"
 
 using IntMap = Core::HashMap<int, int>;
+using String = Core::ArrayString<128>;
+
+template<typename T>
+static String build(Core::Test& test, const T& t) {
+    String s;
+    test.checkFalse(s.append(t), "append works");
+    return s;
+}
 
 static void testAdd(Core::Test& test) {
     IntMap map;
@@ -93,8 +101,10 @@ struct A {
         return a == other.a && b == other.b;
     }
 
-    void toString(Core::String& s) const {
-        s.append("A(").append(a).append(", ").append(b).append(")");
+    template<int N>
+    check_return bool toString(Core::ArrayString<N>& s) const {
+        return s.append("A(") || s.append(a) || s.append(", ") || s.append(b) ||
+               s.append(")");
     }
 };
 
@@ -133,19 +143,19 @@ static void testToString1(Core::Test& test) {
     test.checkFalse(map.add(1, 3), "add works to string 1");
     test.checkFalse(map.add(2, 4), "add works to string 2");
     test.checkFalse(map.add(3, 5), "add works to string 3");
-    test.checkEqual(Core::String("[1 = 3, 2 = 4, 3 = 5]"), Core::String(map),
+    test.checkEqual(build(test, "[1 = 3, 2 = 4, 3 = 5]"), build(test, map),
                     "to string 1");
 }
 
 static void testToString2(Core::Test& test) {
     IntMap map;
     test.checkFalse(map.add(1, 3), "add works to string 4");
-    test.checkEqual(Core::String("[1 = 3]"), Core::String(map), "to string 2");
+    test.checkEqual(build(test, "[1 = 3]"), build(test, map), "to string 2");
 }
 
 static void testToString3(Core::Test& test) {
     IntMap map;
-    test.checkEqual(Core::String("[]"), Core::String(map), "to string 3");
+    test.checkEqual(build(test, "[]"), build(test, map), "to string 3");
 }
 
 static void testCopy(Core::Test& test) {

+ 11 - 3
tests/LinkedListTests.cpp

@@ -14,6 +14,14 @@ struct LinkedListTester {
 };
 
 using IntList = Core::LinkedList<int>;
+using String = Core::ArrayString<128>;
+
+template<typename T>
+static String build(Core::Test& test, const T& t) {
+    String s;
+    test.checkFalse(s.append(t), "append works");
+    return s;
+}
 
 static void testWithoutCopyOrMove(Core::Test& test) {
     Core::LinkedList<LinkedListTester> list;
@@ -117,19 +125,19 @@ static void testToString1(Core::Test& test) {
     test.checkFalse(list.add(1) == nullptr, "add works 20");
     test.checkFalse(list.add(243) == nullptr, "add works 21");
     test.checkFalse(list.add(-423) == nullptr, "add works 22");
-    test.checkEqual(Core::String("[1, 243, -423]"), Core::String(list),
+    test.checkEqual(build(test, "[1, 243, -423]"), build(test, list),
                     "to string 1");
 }
 
 static void testToString2(Core::Test& test) {
     IntList list;
     test.checkFalse(list.add(1) == nullptr, "add works 23");
-    test.checkEqual(Core::String("[1]"), Core::String(list), "to string 2");
+    test.checkEqual(build(test, "[1]"), build(test, list), "to string 2");
 }
 
 static void testToString3(Core::Test& test) {
     IntList list;
-    test.checkEqual(Core::String("[]"), Core::String(list), "to string 3");
+    test.checkEqual(build(test, "[]"), build(test, list), "to string 3");
 }
 
 static void testRemove(Core::Test& test) {

+ 11 - 3
tests/ListTests.cpp

@@ -4,6 +4,14 @@
 #include "test/Test.h"
 
 using IntList = Core::List<int>;
+using String = Core::ArrayString<128>;
+
+template<typename T>
+static String build(Core::Test& test, const T& t) {
+    String s;
+    test.checkFalse(s.append(t), "append works");
+    return s;
+}
 
 static void testAdd(Core::Test& test) {
     IntList list;
@@ -111,19 +119,19 @@ static void testToString1(Core::Test& test) {
     test.checkFalse(list.add(1), "add works 20");
     test.checkFalse(list.add(243), "add works 21");
     test.checkFalse(list.add(-423), "add works 22");
-    test.checkEqual(Core::String("[1, 243, -423]"), Core::String(list),
+    test.checkEqual(build(test, "[1, 243, -423]"), build(test, list),
                     "to string 1");
 }
 
 static void testToString2(Core::Test& test) {
     IntList list;
     test.checkFalse(list.add(1), "add works 23");
-    test.checkEqual(Core::String("[1]"), Core::String(list), "to string 2");
+    test.checkEqual(build(test, "[1]"), build(test, list), "to string 2");
 }
 
 static void testToString3(Core::Test& test) {
     IntList list;
-    test.checkEqual(Core::String("[]"), Core::String(list), "to string 3");
+    test.checkEqual(build(test, "[]"), build(test, list), "to string 3");
 }
 
 static void testRemoveBySwap(Core::Test& test) {

+ 0 - 248
tests/StringTests.cpp

@@ -1,248 +0,0 @@
-#include "tests/StringTests.h"
-
-#include "test/Test.h"
-#include "utils/String.h"
-
-// #include "data/HashMap.h"
-
-static void testEquality(Core::Test& test) {
-    Core::String s("test");
-    test.checkTrue(s == "test", "equality with c-string");
-    test.checkTrue(s == Core::String("test"), "equality with another string");
-    test.checkTrue("test" == s, "inverse equality with c-string");
-    test.checkTrue(Core::String("test") == s,
-                   "inverse equality with another string");
-    test.checkTrue(s == s, "equality with itself");
-}
-
-static void testInequality(Core::Test& test) {
-    Core::String s("test");
-    test.checkFalse(s != "test", "inequality with c-string");
-    test.checkFalse(s != Core::String("test"),
-                    "inequality with another string");
-    test.checkFalse("test" != s, "inverse inequality with c-string");
-    test.checkFalse(Core::String("test") != s,
-                    "inverse inequality with another string");
-    test.checkFalse(s != s, "inequality with itself");
-}
-
-static void testStringAppend(Core::Test& test) {
-    Core::String s("test");
-    s.append("22").append("333").append("4444");
-    test.checkEqual(Core::String("test223334444"), s, "multiple appends");
-}
-
-static void testCharacters(Core::Test& test) {
-    Core::String s("test");
-    test.checkEqual<u32>('t', s[0], "character read 1");
-    test.checkEqual<u32>('e', s[1], "character read 2");
-    test.checkEqual<u32>('s', s[2], "character read 3");
-    test.checkEqual<u32>('t', s[3], "character read 4");
-}
-
-static void testLength(Core::Test& test) {
-    Core::String s("test");
-    test.checkEqual(4, s.getLength(), "length 1");
-    s.append("aaa");
-    test.checkEqual(7, s.getLength(), "length 2");
-}
-
-static void testChar(Core::Test& test) {
-    Core::String s("test");
-    for(char i = 'a'; i < 'd'; i++) {
-        s.append(i);
-    }
-    test.checkEqual(Core::String("testabc"), s, "char append");
-}
-
-static void testSignedChar(Core::Test& test) {
-    Core::String s("test");
-    for(signed char i = 'b'; i < 'e'; i++) {
-        s.append(i);
-    }
-    test.checkEqual(Core::String("testbcd"), s, "signed char append");
-}
-
-static void testUnsignedChar(Core::Test& test) {
-    Core::String s("test");
-    for(unsigned char i = 'c'; i < 'f'; i++) {
-        s.append(i);
-    }
-    test.checkEqual(Core::String("testcde"), s, "unsigned char append");
-}
-
-static void testSignedShort(Core::Test& test) {
-    Core::String s("test");
-    for(signed short i = 100; i < 103; i++) {
-        s.append(i);
-    }
-    test.checkEqual(Core::String("test100101102"), s, "signed short append");
-}
-
-static void testUnsignedShort(Core::Test& test) {
-    Core::String s("test");
-    for(unsigned short i = 101; i < 104; i++) {
-        s.append(i);
-    }
-    test.checkEqual(Core::String("test101102103"), s, "unsigned short append");
-}
-
-static void testSignedInt(Core::Test& test) {
-    Core::String s("test");
-    for(signed int i = 102; i < 105; i++) {
-        s.append(i);
-    }
-    test.checkEqual(Core::String("test102103104"), s, "signed int append");
-}
-
-static void testUnsignedInt(Core::Test& test) {
-    Core::String s("test");
-    for(unsigned int i = 103; i < 106; i++) {
-        s.append(i);
-    }
-    test.checkEqual(Core::String("test103104105"), s, "unsigned int append");
-}
-
-static void testSignedLong(Core::Test& test) {
-    Core::String s("test");
-    for(signed long i = 104; i < 107; i++) {
-        s.append(i);
-    }
-    test.checkEqual(Core::String("test104105106"), s, "signed long append");
-}
-
-static void testUnsignedLong(Core::Test& test) {
-    Core::String s("test");
-    for(unsigned long i = 105; i < 108; i++) {
-        s.append(i);
-    }
-    test.checkEqual(Core::String("test105106107"), s, "unsigned long append");
-}
-
-static void testSignedLongLong(Core::Test& test) {
-    Core::String s("test");
-    for(signed long long i = 106; i < 109; i++) {
-        s.append(i);
-    }
-    test.checkEqual(Core::String("test106107108"), s,
-                    "signed long long append");
-}
-
-static void testUnsignedLongLong(Core::Test& test) {
-    Core::String s("test");
-    for(unsigned long long i = 107; i < 110; i++) {
-        s.append(i);
-    }
-    test.checkEqual(Core::String("test107108109"), s,
-                    "unsigned long long append");
-}
-
-static void testFloat(Core::Test& test) {
-    Core::String s("test");
-    for(float i = 108; i < 111; i++) {
-        s.append(i);
-    }
-    test.checkEqual(Core::String("test108.00109.00110.00"), s, "float append");
-}
-
-static void testDouble(Core::Test& test) {
-    Core::String s("test");
-    for(double i = 109; i < 112; i++) {
-        s.append(i);
-    }
-    test.checkEqual(Core::String("test109.00110.00111.00"), s, "double append");
-}
-
-static void testLongDouble(Core::Test& test) {
-    Core::String s("test");
-    for(long double i = 110; i < 113; i++) {
-        s.append(i);
-    }
-    test.checkEqual(Core::String("test110.00111.00112.00"), s,
-                    "long double append");
-}
-
-static void testBool(Core::Test& test) {
-    Core::String s("test");
-    s.append(true).append(false).append(true);
-    test.checkEqual(Core::String("testtruefalsetrue"), s, "bool append");
-}
-
-static void testUnicode(Core::Test& test) {
-    Core::String s;
-    s.appendUnicode('\u0040');
-    s.appendUnicode(L'\u0400');
-    s.appendUnicode(L'\u8000');
-    s.appendUnicode(U'\U00100000');
-    test.checkEqual(Core::String("\u0040\u0400\u8000\U00100000"), s,
-                    "unicode append");
-}
-
-static void testClear(Core::Test& test) {
-    Core::String s("test");
-    s.append(1234).clear().append("wusi").append("1234");
-    test.checkEqual(Core::String("wusi1234"), s, "clear");
-}
-
-static void testHashCode(Core::Test& test) {
-    Core::String s;
-    s.append("a").append("bc").append(20).append(25.5f).append(true);
-    test.checkEqual(Core::String("abc2025.50true").hashCode(), s.hashCode(),
-                    "string modification recalculates hash 1");
-    s.clear();
-    test.checkEqual(Core::String().hashCode(), s.hashCode(),
-                    "string modification recalculates hash 2");
-}
-
-static void testAsHashMapKey(Core::Test& test) {
-    (void)test;
-    /*HashMap<String, int> map;
-    map.add(String("wusi"), 3)
-        .add(String("hiThere"), 7)
-        .add(String("baum123"), 5);
-
-    int* a = map.search(String("wusi"));
-    int* b = map.search(String("hiThere"));
-    int* c = map.search(String("baum123"));
-    int* d = map.search(String("423hifd"));
-
-    test.checkEqual(true, a != nullptr, "strings works as hash key 1");
-    test.checkEqual(true, b != nullptr, "strings works as hash key 2");
-    test.checkEqual(true, c != nullptr, "strings works as hash key 3");
-    test.checkEqual(true, d == nullptr, "strings works as hash key 4");
-
-    if(a != nullptr && b != nullptr && c != nullptr) {
-        test.checkEqual(3, *a, "strings works as hash key 1");
-        test.checkEqual(7, *b, "strings works as hash key 2");
-        test.checkEqual(5, *c, "strings works as hash key 3");
-    }*/
-}
-
-void Core::StringTests::test() {
-    Core::Test test("String");
-    testEquality(test);
-    testInequality(test);
-    testStringAppend(test);
-    testCharacters(test);
-    testLength(test);
-    testChar(test);
-    testSignedChar(test);
-    testUnsignedChar(test);
-    testSignedShort(test);
-    testUnsignedShort(test);
-    testSignedInt(test);
-    testUnsignedInt(test);
-    testSignedLong(test);
-    testUnsignedLong(test);
-    testSignedLongLong(test);
-    testUnsignedLongLong(test);
-    testFloat(test);
-    testDouble(test);
-    testLongDouble(test);
-    testBool(test);
-    testUnicode(test);
-    testClear(test);
-    testHashCode(test);
-    testAsHashMapKey(test);
-    test.finalize();
-}

+ 0 - 8
tests/StringTests.h

@@ -1,8 +0,0 @@
-#ifndef CORE_STRING_TESTS_H
-#define CORE_STRING_TESTS_H
-
-namespace Core::StringTests {
-    void test();
-}
-
-#endif

+ 372 - 0
utils/ArrayString.h

@@ -0,0 +1,372 @@
+#ifndef CORE_STRING_H
+#define CORE_STRING_H
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "math/Math.h"
+#include "utils/Check.h"
+#include "utils/Types.h"
+#include "utils/Utility.h"
+
+namespace Core {
+    template<int N>
+    class ArrayString final {
+    public:
+        int length;
+        Hash hash;
+        static constexpr int DATA_LENGTH =
+            (N - Math::max(IntSize(length), IntSize(hash))) / IntSize(u32);
+        static_assert(DATA_LENGTH > 0, "Size of array string too small");
+        u32 data[static_cast<unsigned int>(DATA_LENGTH)];
+
+    public:
+        ArrayString() : length(0), hash(0) {
+            data[0] = '\0';
+        }
+
+        bool operator==(const char* s) const {
+            for(int i = 0; i < length; i++) {
+                u32 u = 0;
+                if(readUnicode(u, s) || data[i] != u) {
+                    return false;
+                }
+            }
+            return read(s) == 0;
+        }
+
+        template<int L>
+        bool operator==(const ArrayString<L>& other) const {
+            if(length != other.length) {
+                return false;
+            }
+            for(int i = 0; i < length; i++) {
+                if(data[i] != other[i]) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        bool operator!=(const char* s) const {
+            return !((*this) == s);
+        }
+
+        template<int L>
+        bool operator!=(const ArrayString<L>& other) const {
+            return !((*this) == other);
+        }
+
+        u32 operator[](int index) const {
+            return data[index];
+        }
+
+        int getLength() const {
+            return length;
+        }
+
+        constexpr int getCapacity() const {
+            return DATA_LENGTH;
+        }
+
+        // returns true on error
+        check_return bool append(char c) {
+            if(c < 0) {
+                return true;
+            }
+            return appendUnicode(static_cast<u32>(c));
+        }
+
+        // returns true on error
+        check_return bool append(signed char c) {
+            if(c < 0) {
+                return true;
+            }
+            return appendUnicode(static_cast<u32>(c));
+        }
+
+        // returns true on error
+        check_return bool append(unsigned char c) {
+            return appendUnicode(c);
+        }
+
+        // returns true on error
+        check_return bool append(const char* s) {
+            while(true) {
+                u32 u = 0;
+                if(readUnicode(u, s)) {
+                    return true;
+                } else if(u == 0) {
+                    return false;
+                } else if(appendUnicode(u)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        // returns true on error
+        check_return bool append(const signed char* s) {
+            return append(reinterpret_cast<const char*>(s));
+        }
+
+        // returns true on error
+        check_return bool append(const unsigned char* s) {
+            return append(reinterpret_cast<const char*>(s));
+        }
+
+        // returns true on error
+        check_return bool append(signed short s) {
+            return appendFormat("%hd", s);
+        }
+
+        // returns true on error
+        check_return bool append(unsigned short s) {
+            return appendFormat("%hu", s);
+        }
+
+        // returns true on error
+        check_return bool append(signed int i) {
+            return appendFormat("%d", i);
+        }
+
+        // returns true on error
+        check_return bool append(unsigned int i) {
+            return appendFormat("%u", i);
+        }
+
+        // returns true on error
+        check_return bool append(signed long l) {
+            return appendFormat("%ld", l);
+        }
+
+        // returns true on error
+        check_return bool append(unsigned long l) {
+            return appendFormat("%lu", l);
+        }
+
+        // returns true on error
+        check_return bool append(signed long long ll) {
+            return appendFormat("%lld", ll);
+        }
+
+        // returns true on error
+        check_return bool append(unsigned long long ll) {
+            return appendFormat("%llu", ll);
+        }
+
+        // returns true on error
+        check_return bool append(float f) {
+            return appendFormat("%.2f", static_cast<double>(f));
+        }
+
+        // returns true on error
+        check_return bool append(double d) {
+            return appendFormat("%.2f", d);
+        }
+
+        // returns true on error
+        check_return bool append(long double ld) {
+            return appendFormat("%.2Lf", ld);
+        }
+
+        // returns true on error
+        check_return bool append(bool b) {
+            return b ? append("true") : append("false");
+        }
+
+        // returns true on error
+        check_return bool appendUnicode(u32 c) {
+            if(length >= DATA_LENGTH) {
+                return true;
+            }
+            data[length++] = c;
+            addToHash(c);
+            return false;
+        }
+
+        // returns true on error
+        template<typename T>
+        check_return bool append(const T& t) {
+            return t.toString(*this);
+        }
+
+        // returns true on error
+        template<int L>
+        check_return bool toString(ArrayString<L>& s) const {
+            int l = length; // length changes if &s == this
+            for(int i = 0; i < l; i++) {
+                if(s.appendUnicode(data[i])) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        void clear() {
+            length = 0;
+            hash = 0;
+            data[0] = '\0';
+        }
+
+        Hash hashCode() const {
+            return hash;
+        }
+
+        // returns true on error
+        check_return bool print() const {
+            for(int i = 0; i < length; i++) {
+                u32 c = data[i];
+                if(c < (1 << 7)) {
+                    if(putchar(static_cast<int>(c & 0x7F)) == EOF) {
+                        return true;
+                    }
+                } else if(c < (1 << 11)) {
+                    if(printChar(c, 6, 0x1F, 0xC0) ||
+                       printChar(c, 0, 0x3F, 0x80)) {
+                        return true;
+                    }
+                } else if(c < (1 << 16)) {
+                    if(printChar(c, 12, 0x0F, 0xE0) ||
+                       printChar(c, 6, 0x3F, 0x80) ||
+                       printChar(c, 0, 0x3F, 0x80)) {
+                        return true;
+                    }
+                } else if(c < (1 << 21)) {
+                    if(printChar(c, 18, 0x07, 0xF0) ||
+                       printChar(c, 12, 0x3F, 0x80) ||
+                       printChar(c, 6, 0x3F, 0x80) ||
+                       printChar(c, 0, 0x3F, 0x80)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        // returns true on error
+        check_return bool printLine() const {
+            return print() || putchar('\n') == EOF;
+        }
+
+        // returns true on error
+        template<typename... Args>
+        check_return bool format(Args&&... args) {
+            ArrayString s;
+            if(formatBuffer(s, 0, Core::forward<Args>(args)...)) {
+                return true;
+            }
+            *this = s;
+            return false;
+        }
+
+    private:
+        static bool printChar(u32 u, u32 shift, u32 a, u32 o) {
+            return putchar(static_cast<int>(((u >> shift) & a) | o)) == EOF;
+        }
+        static u32 read(const char*& s) {
+            if(*s == '\0') {
+                return 0;
+            }
+            return static_cast<u32>(*(s++));
+        }
+
+        // returns true on error
+        static bool readUnicode(u32& u, const char*& s) {
+            u = read(s);
+            if((u & 0x80) == 0) {
+                return false;
+            }
+            if((u & 0xE0) == 0xC0) {
+                u32 u2 = read(s);
+                if(u2 == 0) {
+                    return true;
+                }
+                u = ((u & 0x1F) << 6) | (u2 & 0x3F);
+                return false;
+            } else if((u & 0xF0) == 0xE0) {
+                u32 u2 = read(s);
+                u32 u3 = read(s);
+                if(u2 == 0 || u3 == 0) {
+                    return true;
+                }
+                u = ((u & 0xF) << 12) | ((u2 & 0x3F) << 6) | (u3 & 0x3F);
+                return false;
+            } else if((u & 0xF8) == 0xF0) {
+                u32 u2 = read(s);
+                u32 u3 = read(s);
+                u32 u4 = read(s);
+                if(u2 == 0 || u3 == 0 || u4 == 0) {
+                    return true;
+                }
+                u = ((u & 0x07) << 18) | ((u2 & 0x3F) << 12) |
+                    ((u3 & 0x3F) << 6) | (u4 & 0x3F);
+                return false;
+            }
+            return true;
+        }
+
+        void addToHash(u32 u) {
+            hash = static_cast<Hash>(2120251889) * hash + static_cast<Hash>(u);
+        }
+
+        // returns true on error
+        template<typename T, typename... Args>
+        check_return bool formatBuffer(ArrayString& s, int index, const T& t,
+                                       Args&&... args) {
+            while(index < length) {
+                u32 u = data[index++];
+                if(u == '#') {
+                    if(index < length && data[index] != '#') {
+                        break;
+                    }
+                    index++;
+                }
+                if(s.appendUnicode(u)) {
+                    return true;
+                }
+            }
+            if(s.append(t)) {
+                return true;
+            }
+            return formatBuffer(s, index, Core::forward<Args>(args)...);
+        }
+
+        // returns true on error
+        check_return bool formatBuffer(ArrayString& s, int index) {
+            while(index < length) {
+                if(s.appendUnicode(data[index++])) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        // returns true on error
+        check_format(2, 3) check_return
+            bool appendFormat(const char* format, ...) {
+            constexpr int BUFFER_SIZE = 64;
+            char buffer[BUFFER_SIZE];
+
+            va_list args;
+            va_start(args, format);
+            int written = vsnprintf(buffer, BUFFER_SIZE, format, args);
+            va_end(args);
+
+            return written >= BUFFER_SIZE ||
+                   append(static_cast<const char*>(buffer));
+        }
+    };
+}
+
+template<int N>
+bool operator==(const char* cs, const Core::ArrayString<N>& s) {
+    return s == cs;
+}
+
+template<int N>
+bool operator!=(const char* cs, const Core::ArrayString<N>& s) {
+    return s != cs;
+}
+
+#endif

+ 37 - 31
utils/Logger.h

@@ -1,57 +1,63 @@
 #ifndef CORE_LOGGER_H
 #define CORE_LOGGER_H
 
-#include <stdio.h>
-
-#include "utils/String.h"
+#include "utils/ArrayString.h"
 
 namespace Core::Logger {
     enum class Level { ERROR, WARNING, INFO, DEBUG };
     extern Level level;
+
+    // aborts on critical logging failure
+    template<typename... Args>
+    void log(Level l, const char* file, int line, const char* start,
+             const char* format, Args&&... args) {
+        if(Core::Logger::level < l) {
+            return;
+        }
+        Core::ArrayString<2048> s;
+        if(s.append(format)) {
+            printf(
+                "\33[1;31m%s:%d contains an invalid format string\33[39;49m\n",
+                file, line);
+        } else if(s.format(Core::forward<Args>(args)...)) {
+            printf("\33[1;31m%s:%d formatting failed\33[39;49m\n", file, line);
+        } else if(fputs(start, stdout) < 0 || s.print() ||
+                  printf("\33[39;49m\n") <= 0) {
+            Core::exitWithHandler(1);
+        }
+    }
 }
 
 #if defined(LOG_LEVEL) && LOG_LEVEL >= 1
-#define LOG_ERROR(form, ...)                                                   \
-    if(Core::Logger::level >= Core::Logger::Level::ERROR) {                    \
-        printf("\33[1;31m[ERROR] ");                                           \
-        Core::String::format(form, __VA_ARGS__).print();                       \
-        printf("\33[39;49m\n");                                                \
-    }
+#define LOG_ERROR(format, ...)                                                 \
+    log(Core::Logger::Level::ERROR, __FILE__, __LINE__, "\33[1;31m[ERROR] ",   \
+        format, __VA_ARGS__);
 #else
-#define LOG_ERROR(form, ...)
+#define LOG_ERROR(format, ...)
 #endif
 
 #if defined(LOG_LEVEL) && LOG_LEVEL >= 2
-#define LOG_WARNING(form, ...)                                                 \
-    if(Core::Logger::level >= Core::Logger::Level::WARNING) {                  \
-        printf("\33[1;33m[WARNING] ");                                         \
-        Core::String::format(form, __VA_ARGS__).print();                       \
-        printf("\33[39;49m\n");                                                \
-    }
+#define LOG_WARNING(format, ...)                                               \
+    log(Core::Logger::Level::WARNING, __FILE__, __LINE__,                      \
+        "\33[1;33m[WARNING] ", format, __VA_ARGS__);
 #else
-#define LOG_WARNING(form, ...)
+#define LOG_WARNING(format, ...)
 #endif
 
 #if defined(LOG_LEVEL) && LOG_LEVEL >= 3
-#define LOG_INFO(form, ...)                                                    \
-    if(Core::Logger::level >= Core::Logger::Level::INFO) {                     \
-        printf("\33[1;37m[INFO] ");                                            \
-        Core::String::format(form, __VA_ARGS__).print();                       \
-        printf("\33[39;49m\n");                                                \
-    }
+#define LOG_INFO(format, ...)                                                  \
+    log(Core::Logger::Level::INFO, __FILE__, __LINE__, "\33[1;37m[INFO] ",     \
+        format, __VA_ARGS__);
 #else
-#define LOG_INFO(form, ...)
+#define LOG_INFO(format, ...)
 #endif
 
 #if defined(LOG_LEVEL) && LOG_LEVEL >= 4
-#define LOG_DEBUG(form, ...)                                                   \
-    if(Core::Logger::level >= Core::Logger::Level::DEBUG) {                    \
-        printf("\33[1;32m[DEBUG] ");                                           \
-        Core::String::format(form, __VA_ARGS__).print();                       \
-        printf("\33[39;49m\n");                                                \
-    }
+#define LOG_DEBUG(format, ...)                                                 \
+    log(Core::Logger::Level::DEBUG, __FILE__, __LINE__, "\33[1;32m[DEBUG] ",   \
+        format, __VA_ARGS__);
 #else
-#define LOG_DEBUG(form, ...)
+#define LOG_DEBUG(format, ...)
 #endif
 
 #endif

+ 0 - 215
utils/String.cpp

@@ -1,215 +0,0 @@
-#include "utils/String.h"
-
-#include <stdarg.h>
-#include <stdio.h>
-
-Core::String::String() : length(0), hash(0) {
-    data[0] = '\0';
-}
-
-bool Core::String::operator==(const char* s) const {
-    return *this == String(s);
-}
-
-bool Core::String::operator==(const String& other) const {
-    if(length != other.length) {
-        return false;
-    }
-    for(int i = 0; i < length; i++) {
-        if(data[i] != other[i]) {
-            return false;
-        }
-    }
-    return true;
-}
-
-bool Core::String::operator!=(const char* s) const {
-    return !((*this) == s);
-}
-
-bool Core::String::operator!=(const String& other) const {
-    return !((*this) == other);
-}
-
-u32 Core::String::operator[](int index) const {
-    return data[index];
-}
-
-int Core::String::getLength() const {
-    return length;
-}
-
-Core::String& Core::String::append(char c) {
-    return appendUnicode(static_cast<u32>(c < 0 ? '?' : c));
-}
-
-Core::String& Core::String::append(signed char c) {
-    return appendUnicode(static_cast<u32>(c < 0 ? '?' : c));
-}
-
-Core::String& Core::String::append(unsigned char c) {
-    return appendUnicode(c);
-}
-
-static u32 read(const char*& s) {
-    if(*s == '\0') {
-        return 0;
-    }
-    return static_cast<u32>(*(s++));
-}
-
-Core::String& Core::String::append(const char* s) {
-    while(true) {
-        u32 u = read(s);
-        if(u == 0) {
-            break;
-        } else if((u & 0xE0) == 0xC0) {
-            u = ((u & 0x1F) << 6) | (read(s) & 0x3F);
-        } else if((u & 0xF0) == 0xE0) {
-            u = ((u & 0xF) << 12) | ((read(s) & 0x3F) << 6);
-            u |= read(s) & 0x3F;
-        } else if((u & 0xF8) == 0xF0) {
-            u = ((u & 0x07) << 18) | ((read(s) & 0x3F) << 12);
-            u |= (read(s) & 0x3F) << 6;
-            u |= read(s) & 0x3F;
-        }
-        appendUnicode(u);
-    }
-    return *this;
-}
-
-Core::String& Core::String::append(const signed char* s) {
-    return append(reinterpret_cast<const char*>(s));
-}
-
-Core::String& Core::String::append(const unsigned char* s) {
-    return append(reinterpret_cast<const char*>(s));
-}
-
-Core::String& Core::String::append(signed short s) {
-    return appendFormat("%hd", s);
-}
-
-Core::String& Core::String::append(unsigned short s) {
-    return appendFormat("%hu", s);
-}
-
-Core::String& Core::String::append(signed int i) {
-    return appendFormat("%d", i);
-}
-
-Core::String& Core::String::append(unsigned int i) {
-    return appendFormat("%u", i);
-}
-
-Core::String& Core::String::append(signed long l) {
-    return appendFormat("%ld", l);
-}
-
-Core::String& Core::String::append(unsigned long l) {
-    return appendFormat("%lu", l);
-}
-
-Core::String& Core::String::append(signed long long ll) {
-    return appendFormat("%lld", ll);
-}
-
-Core::String& Core::String::append(unsigned long long ll) {
-    return appendFormat("%llu", ll);
-}
-
-Core::String& Core::String::append(float f) {
-    return appendFormat("%.2f", static_cast<double>(f));
-}
-
-Core::String& Core::String::append(double d) {
-    return appendFormat("%.2f", d);
-}
-
-Core::String& Core::String::append(long double ld) {
-    return appendFormat("%.2Lf", ld);
-}
-
-Core::String& Core::String::append(bool b) {
-    return b ? append("true") : append("false");
-}
-
-Core::String& Core::String::appendUnicode(u32 c) {
-    if(length < DATA_LENGTH) {
-        data[length++] = c;
-        addToHash(c);
-    }
-    return *this;
-}
-
-check_format(2, 3) Core::String& Core::String::appendFormat(const char* format,
-                                                            ...) {
-    constexpr int BUFFER_SIZE = 64;
-    char buffer[BUFFER_SIZE];
-
-    va_list args;
-    va_start(args, format);
-    vsnprintf(buffer, BUFFER_SIZE, format, args);
-    va_end(args);
-
-    append(static_cast<const char*>(buffer));
-    return *this;
-}
-
-void Core::String::toString(String& s) const {
-    if(this == &s) {
-        return;
-    }
-    for(int i = 0; i < length; i++) {
-        s.appendUnicode(data[i]);
-    }
-}
-
-Core::String& Core::String::clear() {
-    length = 0;
-    hash = 0;
-    data[0] = '\0';
-    return *this;
-}
-
-Hash Core::String::hashCode() const {
-    return hash;
-}
-
-void Core::String::print() const {
-    for(int i = 0; i < length; i++) {
-        u32 c = data[i];
-        if(c < (1 << 7)) {
-            putchar(static_cast<int>(c & 0x7F));
-        } else if(c < (1 << 11)) {
-            putchar(static_cast<int>(((c >> 6) & 0x1F) | 0xC0));
-            putchar(static_cast<int>(((c >> 0) & 0x3F) | 0x80));
-        } else if(c < (1 << 16)) {
-            putchar(static_cast<int>(((c >> 12) & 0x0F) | 0xE0));
-            putchar(static_cast<int>(((c >> 6) & 0x3F) | 0x80));
-            putchar(static_cast<int>(((c >> 0) & 0x3F) | 0x80));
-        } else if(c < (1 << 21)) {
-            putchar(static_cast<int>(((c >> 18) & 0x07) | 0xF0));
-            putchar(static_cast<int>(((c >> 12) & 0x3F) | 0x80));
-            putchar(static_cast<int>(((c >> 6) & 0x3F) | 0x80));
-            putchar(static_cast<int>(((c >> 0) & 0x3F) | 0x80));
-        }
-    }
-}
-
-void Core::String::printLine() const {
-    print();
-    putchar('\n');
-}
-
-void Core::String::addToHash(u32 u) {
-    hash = static_cast<Hash>(2120251889) * hash + static_cast<Hash>(u);
-}
-
-bool operator==(const char* cs, const Core::String& s) {
-    return s == cs;
-}
-
-bool operator!=(const char* cs, const Core::String& s) {
-    return s != cs;
-}

+ 0 - 117
utils/String.h

@@ -1,117 +0,0 @@
-#ifndef CORE_STRING_H
-#define CORE_STRING_H
-
-#include "utils/Check.h"
-#include "utils/Types.h"
-#include "utils/Utility.h"
-
-namespace Core {
-    class String final {
-        int length;
-        Hash hash;
-        static constexpr int DATA_LENGTH =
-            (1024 - (sizeof(length) >= sizeof(hash) ? sizeof(length) * 2
-                                                    : sizeof(hash) * 2)) /
-            sizeof(u32);
-        u32 data[DATA_LENGTH];
-
-    public:
-        String();
-
-        template<typename T>
-        String(const T& t) : String() {
-            append(t);
-        }
-
-        bool operator==(const char* s) const;
-        bool operator==(const String& other) const;
-        bool operator!=(const char* s) const;
-        bool operator!=(const String& other) const;
-        u32 operator[](int index) const;
-
-        int getLength() const;
-
-        String& append(char c);
-        String& append(signed char c);
-        String& append(unsigned char c);
-
-        String& append(const char* s);
-        String& append(const signed char* s);
-        String& append(const unsigned char* s);
-
-        String& append(signed short s);
-        String& append(unsigned short s);
-
-        String& append(signed int i);
-        String& append(unsigned int i);
-
-        String& append(signed long l);
-        String& append(unsigned long l);
-
-        String& append(signed long long ll);
-        String& append(unsigned long long ll);
-
-        String& append(float f);
-        String& append(double d);
-        String& append(long double ld);
-
-        String& append(bool b);
-
-        String& appendUnicode(u32 c);
-        check_format(2, 3) String& appendFormat(const char* format, ...);
-
-        template<typename T>
-        String& append(const T& t) {
-            t.toString(*this);
-            return *this;
-        }
-
-        void toString(String& s) const;
-
-        String& clear();
-        Hash hashCode() const;
-        void print() const;
-        void printLine() const;
-
-        template<typename... Args>
-        static String format(const String& format, Args&&... args) {
-            String s;
-            formatBuffer(s, 0, format, Core::forward<Args>(args)...);
-            return s;
-        }
-
-    private:
-        void addToHash(u32 u);
-
-        template<typename T, typename... Args>
-        static void formatBuffer(String& s, int index, const String& format,
-                                 const T& t, Args&&... args) {
-            while(index < format.getLength()) {
-                u32 u = format[index++];
-                if(u == '#') {
-                    if(index < format.getLength() && format[index] != '#') {
-                        break;
-                    }
-                    index++;
-                }
-                s.appendUnicode(u);
-            }
-            s.append(t);
-            formatBuffer(s, index, format, Core::forward<Args>(args)...);
-        }
-
-        static void formatBuffer(String& s, int index, const String& format) {
-            while(index < format.getLength()) {
-                s.appendUnicode(format[index++]);
-            }
-        }
-    };
-}
-
-static_assert(sizeof(Core::String) == 1024,
-              "String should have a size of 1024 byte");
-
-bool operator==(const char* cs, const Core::String& s);
-bool operator!=(const char* cs, const Core::String& s);
-
-#endif

+ 16 - 1
utils/Utility.cpp

@@ -1,5 +1,8 @@
 #include "utils/Utility.h"
 
+static Core::ExitHandler exitHandler = nullptr;
+static void* exitData = nullptr;
+
 void* operator new(size_t bytes) noexcept {
     return malloc(bytes);
 }
@@ -34,4 +37,16 @@ void* operator new(size_t bytes, void* p) noexcept {
 /*void* operator new[](size_t bytes, void* p) noexcept {
     (void)bytes;
     return p;
-}*/
+}*/
+
+void Core::exitWithHandler(int value) {
+    if(exitHandler != nullptr) {
+        exitHandler(value, exitData);
+    }
+    exit(value);
+}
+
+void Core::setExitHandler(ExitHandler eh, void* data) {
+    exitHandler = eh;
+    exitData = data;
+}

+ 7 - 0
utils/Utility.h

@@ -3,6 +3,8 @@
 
 #include <stdlib.h>
 
+#define IntSize(t) static_cast<int>(sizeof(t))
+
 namespace Core {
     namespace Internal {
         template<typename T>
@@ -69,6 +71,11 @@ namespace Core {
         }
         return sum;
     }
+
+    using ExitHandler = void (*)(int, void*);
+
+    void exitWithHandler(int value);
+    void setExitHandler(ExitHandler eh, void*);
 }
 
 void* operator new(size_t bytes) noexcept;