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