1 module mutils.container.string_intern; 2 3 import mutils.container.hash_map; 4 import mutils.traits : isForeachDelegateWithI; 5 import std.experimental.allocator; 6 import std.experimental.allocator.mallocator; 7 import std.traits : Parameters; 8 9 private __gshared static HashMap!(const(char)[], StringIntern) gStringInterns; 10 11 struct StringIntern { 12 private const(char)* strPtr; 13 14 this(const(char)[] fromStr) { 15 opAssign(fromStr); 16 } 17 18 void reset() { 19 strPtr=null; 20 } 21 22 size_t length() { 23 if (strPtr is null) { 24 return 0; 25 } 26 return *cast(size_t*)(strPtr - 8); 27 } 28 29 const(char)[] str() { 30 if (strPtr is null) { 31 return null; 32 } 33 return strPtr[0 .. length]; 34 } 35 36 const(char)[] cstr() { 37 if (strPtr is null) { 38 return "\0"; 39 } 40 return strPtr[0 .. length + 1]; 41 } 42 43 bool opEquals()(auto ref const StringIntern s) { 44 return strPtr == s.strPtr; 45 } 46 47 bool opEquals()(auto ref const(char[]) s) { 48 return str() == s; 49 } 50 51 void opAssign(const(char)[] fromStr) { 52 if (fromStr.length == 0) { 53 return; 54 } 55 StringIntern defaultValue; 56 StringIntern internedStr = gStringInterns.get(fromStr, defaultValue); 57 58 if (internedStr.length == 0) { 59 internedStr.strPtr = allocStr(fromStr).ptr; 60 gStringInterns.add(internedStr.str, internedStr); 61 } 62 63 strPtr = internedStr.strPtr; 64 } 65 66 const(char)[] opSlice() { 67 if (strPtr is null) { 68 return null; 69 } 70 return strPtr[0 .. length]; 71 } 72 73 private const(char)[] allocStr(const(char)[] fromStr) { 74 char[] data = Mallocator.instance.makeArray!(char)(fromStr.length + size_t.sizeof + 1); 75 size_t* len = cast(size_t*) data.ptr; 76 *len = fromStr.length; 77 data[size_t.sizeof .. $ - 1] = fromStr; 78 data[$ - 1] = '\0'; 79 return data[size_t.sizeof .. $ - 1]; 80 } 81 } 82 83 unittest { 84 static assert(StringIntern.sizeof == size_t.sizeof); 85 const(char)[] chA = ['a', 'a']; 86 char[] chB = ['o', 't', 'h', 'e', 'r']; 87 const(char)[] chC = ['o', 't', 'h', 'e', 'r']; 88 string chD = "other"; 89 90 StringIntern strA; 91 StringIntern strB = StringIntern(""); 92 StringIntern strC = StringIntern("a"); 93 StringIntern strD = "a"; 94 StringIntern strE = "aa"; 95 StringIntern strF = chA; 96 StringIntern strG = chB; 97 98 assert(strA == strB); 99 assert(strA != strC); 100 assert(strC == strD); 101 assert(strD != strE); 102 assert(strE == strF); 103 104 assert(strD.length == 1); 105 assert(strE.length == 2); 106 assert(strG.length == 5); 107 108 strA = "other"; 109 assert(strA == "other"); 110 assert(strA == chB); 111 assert(strA == chC); 112 assert(strA == chD); 113 assert(strA.str.ptr[strA.str.length] == '\0'); 114 assert(strA.cstr[$ - 1] == '\0'); 115 116 foreach (char c; strA) { 117 } 118 foreach (int i, char c; strA) { 119 } 120 foreach (ubyte i, char c; strA) { 121 } 122 foreach (c; strA) { 123 } 124 } 125 126 unittest { 127 import mutils.container.hash_map : HashMap; 128 129 HashMap!(StringIntern, StringIntern) map; 130 131 map.add(StringIntern("aaa"), StringIntern("bbb")); 132 map.add(StringIntern("aaa"), StringIntern("bbb")); 133 134 assert(map.length == 1); 135 assert(map.get(StringIntern("aaa")) == StringIntern("bbb")); 136 137 }