1 /*
2  * Kiss - A refined core library for D programming language.
3  *
4  * Copyright (C) 2015-2018  Shanghai Putao Technology Co., Ltd
5  *
6  * Developer: HuntLabs.cn
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module kiss.container.String;
13 
14 import kiss.container.common;
15 import core.stdc.string : memcpy;
16 import std.traits;
17 import std.experimental.allocator;
18 import std.experimental.allocator.mallocator;
19 import Range = std.range.primitives;
20 
21 alias IString(Alloc) = StringImpl!(char, Alloc);
22 alias IWString(Alloc) = StringImpl!(wchar, Alloc);
23 alias IDString(Alloc) = StringImpl!(dchar, Alloc);
24 alias String = IString!(Mallocator);
25 alias WString = IWString!(Mallocator);
26 alias DString = IDString!(Mallocator);
27 
28 @trusted struct StringImpl(Char, Allocator)
29         if (is(Char == Unqual!Char) && isSomeChar!Char)
30 {
31     alias Data = ArrayCOWData!(Char, Allocator);
32     static if (StaticAlloc!Allocator)
33     {
34         this(const Char[] data)
35         {
36             assign(data);
37         }
38     }
39     else
40     {
41         @disable this();
42         this(const Char[] data, Allocator alloc)
43         {
44             _alloc = alloc;
45             assign(data);
46         }
47 
48         this(Allocator alloc)
49         {
50             _alloc = alloc;
51         }
52     }
53 
54     this(this)
55     {
56         Data.inf(_data);
57     }
58 
59     ~this()
60     {
61         Data.deInf(_alloc, _data);
62     }
63 
64     typeof(this) opSlice() nothrow
65     {
66         return this;
67     }
68 
69     typeof(this) opSlice(in size_t low, in size_t high) @trusted
70     in
71     {
72         assert(low <= high);
73         assert(high < _str.length);
74     }
75     body
76     {
77         auto rv = this;
78         rv._str = _str[low .. high];
79         return rv;
80     }
81 
82     Char opIndex(size_t index) const
83     in
84     {
85         assert(index < _str.length);
86     }
87     body
88     {
89         return _str[index];
90     }
91 
92     bool opEquals(S)(S other) const 
93             if (is(S == Unqual!(typeof(this))) || is(S : const(Char)[]))
94     {
95         if (_str.length == other.length)
96         {
97             for (size_t i = 0; i < _str.length; ++i)
98             {
99                 if (_str[i] != other[i])
100                     return false;
101             }
102             return true;
103         }
104         else
105             return false;
106     }
107 
108     int opCmp(S)(S other) const 
109             if (is(S == Unqual!(typeof(this))) || is(S : const(Char)[]))
110     {
111         auto a = cast(immutable(Char)[]) _str;
112         auto b = cast(immutable(Char)[]) other;
113 
114         if (a < b)
115         {
116             return -1;
117         }
118         else if (a > b)
119         {
120             return 1;
121         }
122         else
123         {
124             return 0;
125         }
126     }
127 
128     size_t opDollar() nothrow const
129     {
130         return _str.length;
131     }
132 
133     mixin AllocDefine!Allocator;
134 
135     void opAssign(S)(auto ref S n)
136             if (is(S == Unqual!(typeof(this))) || is(S : const(Char)[]))
137     {
138         static if (is(S : const Char[]))
139         {
140             assign(n);
141         }
142         else
143         {
144             if (n._data !is _data)
145             {
146                 Data.deInf(_alloc, _data);
147                 _data = n._data;
148                 Data.inf(_data);
149             }
150             _str = n._str;
151         }
152     }
153 
154     @property bool empty() const nothrow
155     {
156         return _str.length == 0;
157     }
158 
159     @property size_t length() const nothrow
160     {
161         return _str.length;
162     }
163 
164     int opApply(scope int delegate(Char) dg)
165     {
166         int result = 0;
167 
168         for (size_t i = 0; i < _str.length; i++)
169         {
170             result = dg(_str[i]);
171             if (result)
172                 break;
173         }
174         return result;
175     }
176 
177     int opApply(scope int delegate(size_t, Char) dg)
178     {
179         int result = 0;
180 
181         for (size_t i = 0; i < _str.length; i++)
182         {
183             result = dg(i, _str[i]);
184             if (result)
185                 break;
186         }
187         return result;
188     }
189 
190     static if (!is(Unqual!Char == dchar))
191     {
192         int opApply(scope int delegate(dchar) dg)
193         {
194             int result = 0;
195             immutable(Char)[] str = cast(immutable(Char)[]) _str;
196             while (!Range.empty(str))
197             {
198                 result = dg(Range.front(str));
199                 if (result)
200                     break;
201                 Range.popFront(str);
202             }
203             return result;
204         }
205     }
206 
207     @property immutable(Char)[] idup() const
208     {
209         return _str.idup;
210     }
211 
212     @property typeof(this) dup()
213     {
214         typeof(this) ret = this;
215         if (this._data !is null)
216             ret.doCOW(0);
217         return ret;
218     }
219 
220     @property auto front() const
221     in
222     {
223         assert(!this.empty);
224     }
225     body
226     {
227         return Range.front(_str);
228     }
229 
230     @property auto back() const
231     in
232     {
233         assert(!this.empty);
234     }
235     body
236     {
237         return Range.back(_str);
238     }
239 
240     @property const(Char)* ptr() const
241     {
242         return _str.ptr;
243     }
244 
245     static if (is(Char == char))
246     {
247         @property const(char)* cstr()
248         {
249             if (_str.length == 0)
250             {
251                 return null;
252             }
253             else
254             {
255                 doCOW(1);
256                 char* ptr = cast(char*) _str.ptr;
257                 ptr[_str.length] = '\0';
258                 return ptr;
259             }
260         }
261     }
262 
263     immutable(Char)[] opCast(T)() nothrow if (is(T == immutable(Char)[]))
264     {
265         return stdString();
266     }
267 
268     @property immutable(Char)[] stdString() nothrow
269     {
270         return cast(immutable(Char)[]) _str;
271     }
272 
273     typeof(this) opBinary(string op, S)(auto ref S other)
274             if ((is(S == Unqual!(typeof(this))) || is(S : const Char[])) && op == "~")
275     {
276         typeof(this) ret = this;
277         ret ~= other;
278         return ret;
279     }
280 
281     void opOpAssign(string op, S)(auto ref S other)
282             if ((is(S == Unqual!(typeof(this))) || is(S : const Char[]) || is(Unqual!S == Char))
283                 && op == "~")
284     {
285         static if (is(Unqual!S == Char))
286         {
287             const size_t tmpLength = 1;
288         }
289         else
290         {
291             if (other.length == 0)
292                 return;
293             const size_t tmpLength = other.length;
294         }
295         doCOW(tmpLength);
296         Char* basePtr = _data.data.ptr + baseLength();
297         Char* tptr = basePtr + _str.length;
298         static if (is(Unqual!S == Char))
299         {
300             tptr[0] = other;
301         }
302         else
303         {
304             memcpy(tptr, other.ptr, (tmpLength * Char.sizeof));
305         }
306         size_t len = _str.length + tmpLength;
307         _str = basePtr[0 .. len];
308     }
309 
310 private:
311     void assign(const Char[] input)
312     {
313         if (input.length == 0)
314         {
315             Data.deInf(_alloc, _data);
316             _str = null;
317             _data = null;
318             return;
319         }
320         auto data = buildData();
321         Data.deInf(_alloc, data);
322         _data.reserve(input.length);
323         size_t len = input.length * Char.sizeof;
324         memcpy(_data.data.ptr, input.ptr, len);
325         _str = _data.data[0 .. input.length];
326     }
327 
328     Data* buildData()
329     {
330         Data* data = null;
331         if (_data !is null && _data.count > 1)
332         {
333             data = _data;
334             _data = null;
335         }
336         if (_data is null)
337         {
338             _data = Data.allocate(_alloc);
339             static if (!StaticAlloc!Allocator)
340                 _data._alloc = _alloc;
341         }
342         return data;
343     }
344 
345     size_t baseLength()
346     {
347         if ((_str.length == 0) || (_str.ptr is _data.data.ptr))
348             return 0;
349         else
350             return cast(size_t)(_str.ptr - _data.data.ptr);
351     }
352 
353     size_t extenSize(size_t size)
354     {
355         if (size > 0)
356             size = size > 128 ? size + ((size / 3) * 2) : size * 2;
357         else
358             size = 32;
359         return size;
360     }
361 
362     void doCOW(size_t tmpLength = 0)
363     {
364         auto data = buildData();
365         if (data !is null)
366         {
367             _data.reserve(extenSize(_str.length + tmpLength));
368             if (_str.length > 0)
369             {
370                 memcpy(_data.data.ptr, _str.ptr, (_str.length * Char.sizeof));
371                 _str = _data.data[0 .. _str.length];
372             }
373             Data.deInf(_alloc, data);
374         }
375         else if (tmpLength > 0)
376         {
377             size_t blen = baseLength();
378             if (_data.reserve(extenSize(blen + _str.length + tmpLength)))
379                 _str = _data.data[blen .. (blen + _str.length)];
380         }
381     }
382 
383 private:
384     Data* _data;
385     Char[] _str;
386 }
387 
388 version (unittest)  : void testFunc(T, size_t Buf)()
389 {
390     import std.conv : to;
391     import std.stdio : writeln;
392     import std.array : empty, popBack, popFront;
393     import std.range.primitives;
394     import std.format : format;
395 
396     auto strs = [
397         "", "ABC", "HellWorld", "", "Foobar",
398         "HellWorldHellWorldHellWorldHellWorldHellWorldHellWorldHellWorldHellWorld",
399         "ABCD", "Hello", "HellWorldHellWorld", "ölleä",
400         "hello\U00010143\u0100\U00010143", "£$€¥", "öhelloöö"
401     ];
402 
403     foreach (strL; strs)
404     {
405         auto str = to!(immutable(T)[])(strL);
406         auto s = String(str);
407 
408         assert(s.length == str.length);
409         assert(s.empty == str.empty);
410         assert(s == str);
411 
412         auto istr = s.idup();
413         assert(str == istr);
414 
415         foreach (it; strs)
416         {
417             auto cmpS = to!(immutable(T)[])(it);
418             auto itStr = String(cmpS);
419 
420             if (cmpS == str)
421             {
422                 assert(s == cmpS);
423                 assert(s == itStr);
424                 assert(s <= itStr);
425             }
426             else
427             {
428                 assert(s != cmpS);
429                 assert(s != itStr);
430             }
431         }
432 
433         if (s.empty)
434         { // if str is empty we do not need to test access
435             continue; //methods
436         }
437 
438         assert(s.front == str.front, to!string(s.front));
439         assert(s.back == str.back);
440         assert(s[0] == str[0], to!string(s[0]) ~ " " ~ to!string(str.front));
441         for (size_t i = 0; i < str.length; ++i)
442         {
443             assert(str[i] == s[i]);
444         }
445 
446         for (size_t it = 0; it < str.length; ++it)
447         {
448             for (size_t jt = it; jt < str.length; ++jt)
449             {
450                 auto ss = s[it .. jt];
451                 auto strc = str[it .. jt];
452 
453                 assert(ss.length == strc.length);
454                 assert(ss.empty == strc.empty);
455 
456                 for (size_t k = 0; k < ss.length; ++k)
457                 {
458                     assert(ss[k] == strc[k], format("it %s jt %s k %s ss[k] %s strc[k] %s str %s",
459                             it, jt, k, ss[k], strc[k], str));
460                 }
461             }
462         }
463 
464         String t;
465         assert(t.empty);
466 
467         t = str;
468         assert(s == t);
469         assert(!t.empty);
470         assert(t.front == str.front, to!string(t.front));
471         assert(t.back == str.back);
472         assert(t[0] == str[0]);
473         assert(t.length == str.length);
474 
475         auto tdup = t.dup;
476         assert(!tdup.empty);
477         assert(tdup.front == str.front, to!string(tdup.front));
478         assert(tdup.back == str.back);
479         assert(tdup[0] == str[0]);
480         assert(tdup.length == str.length);
481 
482         istr = t.idup();
483         assert(str == istr);
484 
485         foreach (it; strs)
486         {
487             auto joinStr = to!(immutable(T)[])(it);
488             auto itStr = String(joinStr);
489             auto compareStr = str ~ joinStr;
490             String tdup22 = tdup;
491             String tdup23 = tdup;
492             tdup22 ~= (joinStr);
493             tdup23 ~= itStr;
494 
495             auto t2dup = tdup ~ joinStr;
496             auto t2dup2 = tdup ~ itStr;
497 
498             assert(t2dup.length == compareStr.length);
499             assert(t2dup2.length == compareStr.length);
500             assert(tdup22.length == compareStr.length);
501             assert(tdup23.length == compareStr.length);
502 
503             assert(t2dup == compareStr);
504             assert(t2dup2 == compareStr);
505             assert(tdup22 == compareStr);
506             assert(tdup23 == compareStr);
507             tdup22 ~= 's';
508             tdup23 ~= 's';
509             assert(tdup22 == tdup23);
510             assert(tdup22 != compareStr);
511             assert(tdup23 != compareStr);
512         }
513     }
514 }
515 
516 unittest
517 {
518     testFunc!(char, 3)();
519 }