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 }