1 module rcstring;
2 
3 import core.stdc.stdlib : realloc, malloc, free;
4 //import core.memory;
5 //import std.conv : emplace;
6 //import std.array : back, front;
7 //import std.traits : Unqual;
8 
9 struct StringPayload(T,M = void) {
10 	T* ptr;
11 	size_t length;
12 	long refCnt;
13 
14 	static if(!is(M == void)) {
15     	ubyte[__traits(classInstanceSize, M)] mutex;
16 	}
17 }
18 
19 struct StringPayloadHandler(T) {
20 	alias Char = T;
21 	alias Payload = StringPayload!T;
22 
23 	static StringPayload!T* make() @trusted {
24 		StringPayload!T* pl;
25 		pl = cast(StringPayload!T*)realloc(pl, typeof(*pl).sizeof);
26 		pl.ptr = null;
27 		pl.length = 0;
28 		pl.refCnt = 1;
29 
30 		return pl;
31 	}
32 
33 	static void allocate(StringPayload!T* pl, in size_t s) @trusted {
34 		//assert(s != 0);
35 		if(s >= pl.length) {
36 			pl.ptr = cast(T*)realloc(pl.ptr, s * T.sizeof);
37 			pl.length = s;
38 		}
39 	}
40 
41 	static void deallocate(StringPayload!T* pl) @trusted {
42 		realloc(pl.ptr, 0);
43 		pl.length = 0;
44 		realloc(pl, 0);
45 	}
46 
47 	static void incrementRefCnt(StringPayload!T* pl) {
48 		if(pl !is null) {
49 			++(pl.refCnt);
50 		}
51 	}
52 
53 	static void decrementRefCnt(StringPayload!T* pl) {
54 		if(pl !is null) {
55 			--(pl.refCnt);
56 			if(pl.refCnt == 0) {
57 				deallocate(pl);
58 			}
59 		}
60 	}
61 }
62 
63 unittest {
64 	auto pl = StringPayloadHandler!char.make();
65 	StringPayloadHandler!char.allocate(pl, 6);
66 	StringPayloadHandler!char.incrementRefCnt(pl);
67 	StringPayloadHandler!char.decrementRefCnt(pl);
68 }
69 
70 struct StringImpl(T,Handler,size_t SmallSize = 16) {
71 	this(immutable(T)[] input) {
72 		this.assign(input);
73 	}
74 
75 	this(typeof(this) n) {
76 		this.assign(n);
77 	}
78 
79 	this(this) {
80 		if(this.large !is null) {
81 			Handler.incrementRefCnt(this.large);
82 		}
83 	}
84 
85 	~this() @trusted {
86 		if(this.large !is null) {
87 			Handler.decrementRefCnt(cast(Handler.Payload*)this.large);
88 		}
89 	}
90 
91 	private void assign(typeof(this) n) @trusted {
92 		if(this.large !is null) {
93 			Handler.decrementRefCnt(this.large);
94 		}
95 
96 		if(n.large !is null) {
97 			this.large = n.large;
98 			Handler.incrementRefCnt(this.large);
99 		} else {
100 			this.small = n.small;
101 		}
102 
103 		this.offset = n.offset;
104 		this.len = n.len;
105 	}
106 
107 	private void assign(immutable(T)[] input) @trusted {
108 		if(input.length > SmallSize) {
109 			this.allocate(input.length);
110 		}
111 
112 		this.storePtr()[0 .. input.length] = input;
113 		this.len = input.length;
114 	}
115 
116 	private void allocate(const size_t newLen) @trusted {
117 		if(newLen > SmallSize) {
118 			if(this.large is null) {
119 				this.large = Handler.make();
120 			}
121 			Handler.allocate(this.large, newLen);
122 		}
123 	}
124 	
125 	private T[] largePtr(in size_t low, in size_t high) @trusted {
126 		return this.large.ptr[low .. high];
127 	}
128 
129 	private bool isSmall() const nothrow {
130 		return this.large is null;
131 	}
132 
133 	private T* storePtr() {
134 		if(this.isSmall()) {
135 			return this.small.ptr;
136 		} else {
137 			return this.large.ptr;
138 		}
139 	}
140 
141 	private const(T)* storePtr() const {
142 		if(this.isSmall()) {
143 			return this.small.ptr;
144 		} else {
145 			return this.large.ptr;
146 		}
147 	}
148 
149 	// properties
150 
151 	@property bool empty() const nothrow {
152 		return this.offset == this.len;
153 	}
154 
155 	@property size_t length() const nothrow {
156 		return cast(size_t)(this.len - this.offset);
157 	}
158 
159 	// compare
160 	
161 	import std.traits : Unqual;
162 
163 	bool opEquals(S)(S other) const 
164 		if(is(S == Unqual!(typeof(this))) ||
165 			is(S == immutable(T)[])
166 		)
167 	{
168 		if(this.length == other.length) {
169 			for(size_t i = 0; i < this.length; ++i) {
170 				if(this[i] != other[i]) {
171 					return false;
172 				}
173 			}
174 
175 			return true;
176 		} else {
177 			return false;
178 		}
179 	}
180 
181 	// dup
182 
183 	@property typeof(this) dup() @trusted {
184 		if(this.isSmall()) {
185 			return this;
186 		} else {
187 			typeof(this) ret;
188 			ret.large = ret.handler.make();
189 			ret.handler.allocate(ret.large, this.large.length);
190 			ret.large.ptr[0 .. this.len - this.offset] =
191 				this.large.ptr[this.offset .. this.len];
192 			ret.offset = 0;
193 			ret.len = this.len - this.offset;
194 
195 			return ret;
196 		}
197 	}
198 
199 	// concat
200 	typeof(this) opBinary(string op,S)(S other) @trusted
201 		if((is(S == Unqual!(typeof(this))) ||
202 			is(S == immutable(T)[])) && op == "~")
203 	{
204 		typeof(this) ret;
205 
206 		const newLen = this.length + other.length;
207 
208 		if(newLen < SmallSize) {
209 			ret.small[0 .. this.length] = 
210 				this.storePtr()[this.offset .. this.len];
211 
212 			ret.offset = 0;
213 			ret.len = this.length;
214 
215 			static if(is(S == immutable(T)[])) {
216 				ret.small[ret.length .. ret.length + other.length] =
217 					other;
218 			} else {
219 				ret.small[ret.length .. ret.length + other.length] =
220 					other.storePtr()[other.offset .. other.len];
221 			}
222 
223 			ret.len = ret.len + other.length;
224 			return ret;
225 		} else {
226 			ret.large = ret.handler.make();
227 			ret.handler.allocate(ret.large, cast(size_t)(newLen * 1.5));
228 
229 			ret.large.ptr[0 .. this.length] = this.isSmall() ? 
230 				this.small[this.offset .. this.len] :
231 				this.largePtr(this.offset, this.len);
232 
233 			ret.offset = 0;
234 			ret.len = this.length;
235 
236 			static if(is(S == immutable(T)[])) {
237 				ret.large.ptr[ret.length .. ret.length + other.length] =
238 					other;
239 			} else {
240 				ret.large.ptr[ret.length .. ret.length + other.length] =
241 					other.storePtr()[other.offset .. other.len];
242 			}
243 
244 			ret.len = ret.len + other.length;
245 			return ret;
246 		}
247 	}
248 
249 	// access
250 
251 	@property T front() const @trusted {
252 		//assert(!this.empty);
253 		return this.storePtr()[this.offset .. this.len][0];
254 	}
255 
256 	@property T back() const @trusted {
257 		//assert(!this.empty);
258 		return this.storePtr()[this.offset .. this.len][$ - 1];
259 	}
260 
261 	@property T opIndex(const size_t idx) const @trusted {
262 		//assert(!this.empty);
263 		//assert(idx < this.len - this.offset);
264 
265 		return this.storePtr()[this.offset .. this.len][idx];
266 	}
267 
268 	typeof(this) opSlice() {
269 		return this;
270 	}
271 
272 	typeof(this) opSlice(in size_t low, in size_t high) @trusted {
273 		//assert(low <= high);
274 		//assert(high < this.length);
275 
276 		if(this.isSmall()) {
277 			return typeof(this)(
278 				cast(immutable(T)[])this.small[
279 					this.offset + low ..  this.offset + high
280 				]
281 			);
282 		} else {
283 			auto ret = typeof(this)(this);
284 			ret.offset += low;
285 			ret.len = this.offset + high;
286 			return ret;
287 		}
288 	}
289 
290 	/*immutable(T)[] idup() @trusted const {
291 		return this.storePtr()[this.offset .. this.offset + this.len].idup;
292 	}*/
293 
294 	// assign
295 
296 	void opAssign(inout(T)[] n) {
297 		if(this.isSmall() && n.length < SmallSize) {
298 			this.small[0 .. n.length] = n;
299 		} else {
300 			if(this.large is null || this.large.refCnt > 1) {
301 				this.large = Handler.make();
302 			}
303 
304 			Handler.allocate(this.large, n.length);
305 			this.largePtr(0, n.length)[] = n;
306 		}
307 
308 		this.len = n.length;
309 		this.offset = 0;
310 	}
311 
312 	void opAssign(typeof(this) n) {
313 		this.assign(n);
314 	}
315 
316 	// modify
317 
318 	/*void popFront() {
319 		import std.utf : stride;
320 
321 		const auto l = this.isSmall() ? 
322 			this.small[this.offset .. this.len].stride() :
323 			this.largePtr(this.offset, this.len).stride();
324 
325 		this.offset += l;
326 	}*/
327 
328 	/*void popBack() {
329 		import std.utf : strideBack;
330 
331 		const auto l = this.isSmall() ? 
332 			this.small[this.offset .. this.len].strideBack() :
333 			this.largePtr(this.offset, this.len).strideBack();
334 
335 		this.len -= l;
336 	}*/
337 
338 	void moveToFront() {
339  		if(this.offset > 0) {
340 			immutable len = this.length;
341 			if(this.isSmall()) {
342 				for(int i = 0; i < len; ++i) {
343 					this.small[i] = this.small[this.offset + i];
344 				}
345 			} else {
346 				for(int i = 0; i < len; ++i) {
347 					(() @trusted => 
348 					this.large.ptr[i] = this.large.ptr[this.offset + i]
349 					)();
350 				}
351 			}
352 			this.offset = 0;
353 			this.len = len;
354 		}
355 	}
356 
357 	import std.traits : isSomeChar;
358 
359 	/*void opIndexAssign(S)(S s, const size_t i) @trusted if(isSomeChar!S) {
360 		import std.utf : decode, encode;
361 		import std.conv : to;
362 		this.moveToFront();
363 
364 		size_t iCopy = i;
365 		T[4 / T.sizeof] correctEncoding;
366 		const size_t toReplaceLen = encode(correctEncoding, 
367 				decode(this.storePtr()[0 .. this.len], iCopy)
368 		);
369 
370 		const size_t replacementLen = encode(correctEncoding, s);
371 
372 		T* ptr;
373 		if(replacementLen > toReplaceLen) {
374 			this.allocate(this.len + (replacementLen - toReplaceLen));
375 			ptr = this.storePtr();
376 			for(int j = to!int(this.length + (replacementLen - toReplaceLen)); j != i;
377 					--j)
378 			{
379 				ptr[j] = ptr[j-1];
380 			}
381 		} else if(replacementLen < toReplaceLen) {
382 			ptr = this.storePtr();
383 			for(int j = to!int(i + replacementLen); j < this.length - 1; ++j) {
384 				ptr[j] = ptr[j + 1];
385 			}
386 		} else {
387 			ptr = this.storePtr();
388 		}
389 		this.len += (replacementLen - toReplaceLen);
390 
391 		for(int j = 0; j < replacementLen; ++j) {
392 			ptr[i + j] = correctEncoding[j];
393 		}
394 	}*/
395 
396 	T[SmallSize] small;
397 	Handler.Payload* large;
398 	Handler handler;
399 
400 	ptrdiff_t offset;
401 	ptrdiff_t len;
402 }
403 
404 public alias String = StringImpl!(char, StringPayloadHandler!char, 12);
405 public alias WString = StringImpl!(wchar, StringPayloadHandler!wchar, 6);
406 public alias DString = StringImpl!(dchar, StringPayloadHandler!dchar, 3);
407 
408 void testFunc(T,size_t Buf)() {
409 	auto strs = ["","ABC", "HellWorld", "", "Foobar", 
410 		"HellWorldHellWorldHellWorldHellWorldHellWorldHellWorldHellWorldHellWorld", 
411 		"ABCD", "Hello", "HellWorldHellWorld", "ölleä",
412 		"hello\U00010143\u0100\U00010143", "£$€¥", "öhelloöö"
413 	];
414 
415 	alias TString = 
416 		StringImpl!(T, StringPayloadHandler!T, Buf);
417 
418 	foreach(strL; strs) {
419 		auto str = to!(immutable(T)[])(strL);
420 		auto s = TString(str);
421 
422 		assert(s.length == str.length);
423 		assert(s.empty == str.empty);
424 		assert(s == str);
425 
426 		auto istr = s.idup();
427 		assert(str == istr);
428 
429 		foreach(it; strs) {
430 			auto cmpS = cast(immutable(T)[])(it);
431 			auto itStr = TString(cmpS);
432 
433 			if(cmpS == str) {
434 				assert(s == cmpS);
435 				assert(s == itStr);
436 			} else {
437 				assert(s != cmpS);
438 				assert(s != itStr);
439 			}
440 		}
441 
442 		if(s.empty) { // if str is empty we do not need to test access
443 			continue; //methods
444 		}
445 
446 		assert(s.front == str.front);
447 		assert(s.back == str.back);
448 		assert(s[0] == str[0]);
449 		for(size_t i = 0; i < str.length; ++i) {
450 			assert(str[i] == s[i]);
451 		}
452 
453 		for(size_t it = 0; it < str.length; ++it) {
454 			for(size_t jt = it; jt < str.length; ++jt) {
455 				auto ss = s[it .. jt];
456 				auto strc = str[it .. jt];
457 
458 				assert(ss.length == strc.length);
459 				assert(ss.empty == strc.empty);
460 
461 				for(size_t k = 0; k < ss.length; ++k) {
462 					assert(ss[k] == strc[k]);
463 				}
464 			}
465 		}
466 
467 		TString t;
468 		assert(t.empty);
469 
470 		t = str;
471 		assert(s == t);
472 		assert(!t.empty);
473 		assert(t.front == str.front);
474 		assert(t.back == str.back);
475 		assert(t[0] == str[0]);
476 		assert(t.length == str.length);
477 
478 		auto tdup = t.dup;
479 		assert(!tdup.empty);
480 		assert(tdup.front == str.front);
481 		assert(tdup.back == str.back);
482 		assert(tdup[0] == str[0]);
483 		assert(tdup.length == str.length);
484 
485 		istr = t.idup();
486 		assert(str == istr);
487 		
488 		if(tdup.large !is null) {
489 			assert(tdup.large.refCnt == 1);
490 		}
491 
492 		foreach(it; strs) {
493 			auto joinStr = cast(immutable(T)[])(it);
494 			auto itStr = TString(joinStr);
495 			auto compareStr = str ~ joinStr;
496 
497 			auto t2dup = tdup ~ joinStr;
498 			auto t2dup2 = tdup ~ itStr;
499 
500 			assert(t2dup.length == compareStr.length);
501 			assert(t2dup2.length == compareStr.length);
502 
503 			assert(t2dup == compareStr);
504 			assert(t2dup2 == compareStr);
505 		}
506 
507 		s = t;
508 		assert(!s.empty);
509 		assert(s.front == str.front);
510 		assert(s.back == str.back);
511 		assert(s[0] == str[0]);
512 		assert(s.length == str.length);
513 
514 		auto r = TString(s);
515 		assert(!r.empty);
516 		assert(r.front == str.front);
517 		assert(r.back == str.back);
518 		assert(r[0] == str[0]);
519 		assert(r.length == str.length);
520 
521 		auto g = r[];
522 		assert(!g.empty);
523 		assert(g.front == str.front);
524 		assert(g.back == str.back);
525 		assert(g[0] == str[0]);
526 		assert(g.length == str.length);
527 
528 		auto strC = str;
529 		auto strC2 = str;
530 		assert(!strC.empty);
531 		assert(!strC2.empty);
532 
533 		r.popFront();
534 		str.popFront();
535 		assert(str.front == r.front);
536 		assert(s != r);
537 
538 		r.popBack();
539 		str.popBack();
540 		assert(str.back == r.back);
541 		assert(str.front == r.front);
542 
543 		assert(!strC.empty);
544 		assert(!s.empty);
545 		while(!strC.empty && !s.empty) {
546 			assert(strC.front == s.front);
547 			assert(strC.back == s.back);
548 			assert(strC.length == s.length);
549 			for(size_t i = 0; i < strC.length; ++i) {
550 				assert(strC[i] == s[i]);
551 			}
552 
553 			strC.popFront();
554 			s.popFront();
555 		}
556 
557 		assert(strC.empty);
558 		assert(s.empty);
559 
560 		assert(!strC2.empty);
561 		assert(!t.empty);
562 		while(!strC2.empty && !t.empty) {
563 			assert(strC2.front == t.front);
564 			assert(strC2.back == t.back);
565 			assert(strC2.length == t.length);
566 			for(size_t i = 0; i < strC2.length; ++i) {
567 				assert(strC2[i] == t[i]);
568 			}
569 
570 			strC2.popFront();
571 			t.popFront();
572 			t.moveToFront();
573 
574 			auto idup2 = t.idup;
575 			assert(t == idup2);
576 			assert(t == strC2);
577 		}
578 
579 		assert(strC2.empty);
580 		assert(t.empty);
581 	}
582 }
583 
584 unittest {
585 	testFunc!(char,3)();
586 }
587 
588 @safe unittest {
589 	import std.meta : AliasSeq;
590 
591 	foreach(Buf; AliasSeq!(1,2,3,4,8,9,12,16,20,21)) {
592 		testFunc!(char,Buf)();
593 		testFunc!(wchar,Buf)();
594 		testFunc!(dchar,Buf)();
595 	}
596 }
597 
598 @system unittest {
599 	String s = "Super Duper ultra long String";
600 	const String s2 = s;
601 }
602 
603 @system unittest {
604 	String s = "Super";
605 	s[0] = 'A';
606 	assert(s == "Auper");
607 	//string dc = "Ä";
608 	//s[0] = dc[0];
609 	//assert(s == "Äuper");
610 }
611 
612 /+
613 @system unittest {
614 	import std.meta : AliasSeq;
615 	foreach(T; AliasSeq!(string,wstring,dstring)) {
616 		String s = "Super Duper ultra long String";
617 		s[0] = 'A';
618 		assert(s == "Auper Duper ultra long String");
619 
620 		dchar dc = 'ä';
621 		s[1] = dc;
622 		assert(s == "Aäper Duper ultra long String");
623 		s.popFront();
624 
625 		dc = 'u';
626 		s[0] = dc;
627 		assert(s == "uper Duper ultra long String");
628 	}
629 }+/