1 module rcstring;
2 
3 import core.memory;
4 import std.conv : emplace;
5 import std.array : back, front;
6 import std.traits : Unqual;
7 
8 
9 pure @safe:
10 
11 struct StringPayload(T) {
12 	T* ptr;
13 	size_t length;
14 	long refCnt;
15 }
16 
17 struct StringPayloadSingleThreadHandler(T) {
18 	alias Char = T;
19 
20 	static StringPayload!T* make() @trusted {
21 		StringPayload!T* pl;
22 		pl = cast(StringPayload!T*)GC.realloc(pl, typeof(*pl).sizeof);
23 		pl.ptr = null;
24 		pl.length = 0;
25 		pl.refCnt = 1;
26 
27 		return pl;
28 	}
29 
30 	static void allocate(StringPayload!T* pl, in size_t s) @trusted {
31 		import std.range : ElementEncodingType;
32 		import std.traits : Unqual;
33 
34 		assert(s != 0);
35 		if(s >= pl.length) {
36 			pl.ptr = cast(T*)GC.realloc(pl.ptr, s * T.sizeof);
37 			pl.length = s;
38 		}
39 	}
40 
41 	static void deallocate(StringPayload!T* pl) @trusted {
42 		GC.realloc(pl.ptr, 0);
43 		pl.length = 0;
44 		GC.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 struct StringPayloadMultiThreadHandler(T) {
64 	import core.sync.mutex : Mutex;
65 
66 	alias Char = T;
67     ubyte[__traits(classInstanceSize, Mutex)] mutex;
68 
69 	static StringPayload!T* make() @trusted {
70 		auto mu = emplace!Mutex(this.mutex);
71 		StringPayload!T* pl;
72 		synchronized(mutex) {
73 			pl = cast(StringPayload!T*)GC.realloc(pl, typeof(*pl).sizeof);
74 			pl.ptr = null;
75 			pl.length = 0;
76 			pl.refCnt = 1;
77 		}
78 
79 		return pl;
80 	}
81 
82 	static void allocate(StringPayload!T* pl, in size_t s) @trusted {
83 		import std.range : ElementEncodingType;
84 		import std.traits : Unqual;
85 
86 		assert(s != 0);
87 		auto mu = cast(Mutex)this.mutex;
88 		synchronized(mutex) {
89 			if(s >= pl.length) {
90 				pl.ptr = cast(T*)GC.realloc(pl.ptr, s * T.sizeof);
91 				pl.length = s;
92 			}
93 		}
94 	}
95 
96 	static void deallocate(StringPayload!T* pl) @trusted {
97 		GC.realloc(pl.ptr, 0);
98 		pl.length = 0;
99 		GC.realloc(pl, 0);
100 	}
101 
102 	static void incrementRefCnt(StringPayload!T* pl) {
103 		auto mu = cast(Mutex)this.mutex;
104 		synchronized(mutex) {
105 			if(pl !is null) {
106 				++(pl.refCnt);
107 			}
108 		}
109 	}
110 
111 	static void decrementRefCnt(StringPayload!T* pl) {
112 		auto mu = cast(Mutex)this.mutex;
113 		synchronized(mutex) {
114 			if(pl !is null) {
115 				--(pl.refCnt);
116 			}
117 		}
118 
119 		if(pl !is null) {
120 			if(pl.refCnt == 0) {
121 				deallocate(pl);
122 			}
123 		}
124 	}
125 }
126 
127 unittest {
128 	auto pl = StringPayloadSingleThreadHandler!char.make();
129 	StringPayloadSingleThreadHandler!char.allocate(pl, 6);
130 	StringPayloadSingleThreadHandler!char.incrementRefCnt(pl);
131 	StringPayloadSingleThreadHandler!char.decrementRefCnt(pl);
132 }
133 
134 struct StringImpl(T,Handler,size_t SmallSize = 16) {
135 	this(immutable(T)[] input) {
136 		this.assign(input);
137 	}
138 
139 	this(typeof(this) n) {
140 		this.assign(n);
141 	}
142 
143 	this(this) {
144 		if(this.large !is null) {
145 			Handler.incrementRefCnt(this.large);
146 		}
147 	}
148 
149 	~this() {
150 		if(this.large !is null) {
151 			Handler.decrementRefCnt(this.large);
152 		}
153 	}
154 
155 	private void assign(typeof(this) n) @safe {
156 		if(this.large !is null) {
157 			Handler.decrementRefCnt(this.large);
158 		}
159 
160 		if(n.large !is null) {
161 			this.large = n.large;
162 			Handler.incrementRefCnt(this.large);
163 		} else {
164 			this.small = n.small;
165 		}
166 
167 		this.offset = n.offset;
168 		this.len = n.len;
169 	}
170 
171 	private void assign(immutable(T)[] input) @trusted {
172 		if(input.length < SmallSize) {
173 			this.small[0 .. input.length] = input;
174 		} else {
175 			this.large = Handler.make();
176 			Handler.allocate(this.large, input.length);
177 			this.large.ptr[0 .. input.length] = input;
178 		}
179 
180 		this.len = input.length;
181 	}
182 	
183 	private T[] largePtr(in size_t low, in size_t high) @trusted {
184 		return this.large.ptr[low .. high];
185 	}
186 
187 	private const(T)[] largePtr(in size_t low, in size_t high) const @trusted {
188 		return this.large.ptr[low .. high];
189 	}
190 
191 	private bool isSmall() const nothrow {
192 		return this.large is null;
193 	}
194 
195 	// properties
196 
197 	@property bool empty() const nothrow {
198 		return this.offset == this.len;
199 	}
200 
201 	@property size_t length() const nothrow {
202 		return cast(size_t)(this.len - this.offset);
203 	}
204 
205 	// compare
206 
207 	bool opEquals(S)(S other) const 
208 		if(is(S == Unqual!(typeof(this))) ||
209 			is(S == immutable(T)[])
210 		)
211 	{
212 		if(this.length == other.length) {
213 			for(size_t i = 0; i < this.length; ++i) {
214 				if(this[i] != other[i]) {
215 					return false;
216 				}
217 			}
218 
219 			return true;
220 		} else {
221 			return false;
222 		}
223 	}
224 
225 	// dup
226 	@property typeof(this) idup() @trusted nothrow {
227 		if(this.isSmall()) {
228 			return this;
229 		} else {
230 			typeof(this) ret;
231 			ret.large = ret.handler.make();
232 			ret.handler.allocate(ret.large, this.large.length);
233 			ret.large.ptr[0 .. this.len - this.offset] =
234 				this.large.ptr[this.offset .. this.len];
235 			ret.offset = 0;
236 			ret.len = this.len - this.offset;
237 
238 			return ret;
239 		}
240 	}
241 
242 	// access
243 
244 	@property auto front() const {
245 		assert(!this.empty);
246 
247 		return this.isSmall() ? 
248 			this.small[this.offset .. this.len].front : 
249 			this.largePtr(this.offset, this.len).front;
250 	}
251 
252 	@property auto back() const {
253 		assert(!this.empty);
254 
255 		return this.isSmall() ? 
256 			this.small[this.offset .. this.len].back : 
257 			this.largePtr(this.offset, this.len).back;
258 	}
259 
260 	@property T opIndex(const size_t idx) const {
261 		assert(!this.empty);
262 		assert(idx < this.len - this.offset);
263 
264 		return this.isSmall() ? 
265 			this.small[this.offset .. this.len][idx] : 
266 			this.largePtr(this.offset, this.len)[idx];
267 	}
268 
269 	typeof(this) opSlice() {
270 		return this;
271 	}
272 
273 	typeof(this) opSlice(in size_t low, in size_t high) @trusted {
274 		assert(low <= high);
275 		assert(high < this.length);
276 
277 		if(this.isSmall()) {
278 			return typeof(this)(
279 				cast(immutable(T)[])this.small[this.offset + low ..  this.offset + high]
280 			);
281 		} else {
282 			auto ret = typeof(this)(this);
283 			ret.offset += low;
284 			ret.len = this.offset + high;
285 			return ret;
286 		}
287 	}
288 
289 	// assign
290 
291 	void opAssign(inout(T)[] n) {
292 		if(this.isSmall() && n.length < SmallSize) {
293 			this.small[0 .. n.length] = n;
294 		} else {
295 			if(this.large is null || this.large.refCnt > 1) {
296 				this.large = Handler.make();
297 			}
298 
299 			Handler.allocate(this.large, n.length);
300 			this.largePtr(0, n.length)[] = n;
301 		}
302 
303 		this.len = n.length;
304 		this.offset = 0;
305 	}
306 
307 	void opAssign(typeof(this) n) {
308 		this.assign(n);
309 	}
310 
311 	// modify
312 
313 	void popFront() {
314 		import std.utf : stride;
315 
316 		const auto l = this.isSmall() ? 
317 			this.small[this.offset .. this.len].stride() :
318 			this.largePtr(this.offset, this.len).stride();
319 
320 		this.offset += l;
321 	}
322 
323 	void popBack() {
324 		import std.utf : strideBack;
325 
326 		const auto l = this.isSmall() ? 
327 			this.small[this.offset .. this.len].strideBack() :
328 			this.largePtr(this.offset, this.len).strideBack();
329 
330 		this.len -= l;
331 
332 	}
333 
334 	T[SmallSize] small;
335 	StringPayload!T* large;
336 	Handler handler;
337 
338 	ptrdiff_t offset;
339 	ptrdiff_t len;
340 }
341 
342 alias String = StringImpl!(char, StringPayloadSingleThreadHandler!char, 12);
343 alias WString = StringImpl!(wchar, StringPayloadSingleThreadHandler!wchar, 6);
344 alias DString = StringImpl!(dchar, StringPayloadSingleThreadHandler!dchar, 3);
345 
346 void testFunc(T,size_t Buf)() {
347 	import std.conv : to;
348 	import std.stdio : writeln;
349 	import std.array : empty, popBack, popFront;
350 	import std.format : format;
351 
352 	auto strs = ["ABC", "HellWorld", "", "Foobar", 
353 		"HellWorldHellWorldHellWorldHellWorldHellWorldHellWorldHellWorldHellWorld", 
354 		"ABCD", "Hello", "HellWorldHellWorld", "ölleä",
355 		"hello\U00010143\u0100\U00010143", "£$€¥", "öhelloöö"
356 	];
357 
358 	alias TString = 
359 		StringImpl!(T, StringPayloadSingleThreadHandler!T, Buf);
360 
361 	foreach(strL; strs) {
362 		auto str = to!(immutable(T)[])(strL);
363 		//debug writeln(str);
364 		auto s = TString(str);
365 		assert(s.length == str.length);
366 		assert(s.empty == str.empty);
367 		assert(s == str);
368 
369 		if(s.empty) { // if str is empty we do not need to test access
370 			continue; //methods
371 		}
372 
373 		assert(s.front == str.front, to!string(s.front));
374 		assert(s.back == str.back);
375 		assert(s[0] == str[0], to!string(s[0]) ~ " " ~ to!string(str.front));
376 		for(size_t i = 0; i < str.length; ++i) {
377 			assert(str[i] == s[i]);
378 		}
379 
380 		for(size_t it = 0; it < str.length; ++it) {
381 			for(size_t jt = it; jt < str.length; ++jt) {
382 				auto ss = s[it .. jt];
383 				auto strc = str[it .. jt];
384 
385 				assert(ss.length == strc.length);
386 				assert(ss.empty == strc.empty);
387 
388 				for(size_t k = 0; k < ss.length; ++k) {
389 					assert(ss[k] == strc[k], 
390 						format("it %s jt %s k %s ss[k] %s strc[k] %s str %s",
391 							it, jt, k, ss[k], strc[k], str
392 						)
393 					);
394 				}
395 			}
396 		}
397 
398 		TString t;
399 		assert(t.empty);
400 
401 		t = str;
402 		assert(s == t);
403 		assert(!t.empty);
404 		assert(t.front == str.front, to!string(t.front));
405 		assert(t.back == str.back);
406 		assert(t[0] == str[0]);
407 		assert(t.length == str.length);
408 
409 		auto tdup = t.idup;
410 		assert(!tdup.empty);
411 		assert(tdup.front == str.front, to!string(tdup.front));
412 		assert(tdup.back == str.back);
413 		assert(tdup[0] == str[0]);
414 		assert(tdup.length == str.length);
415 		
416 		if(tdup.large !is null) {
417 			assert(tdup.large.refCnt == 1);
418 		}
419 
420 		s = t;
421 		assert(!s.empty);
422 		assert(s.front == str.front, to!string(t.front));
423 		assert(s.back == str.back);
424 		assert(s[0] == str[0]);
425 		assert(s.length == str.length);
426 
427 		auto r = TString(s);
428 		assert(!r.empty);
429 		assert(r.front == str.front, to!string(t.front));
430 		assert(r.back == str.back);
431 		assert(r[0] == str[0]);
432 		assert(r.length == str.length);
433 
434 		auto g = r[];
435 		assert(!g.empty);
436 		assert(g.front == str.front, to!string(t.front));
437 		assert(g.back == str.back);
438 		assert(g[0] == str[0]);
439 		assert(g.length == str.length);
440 
441 		auto strC = str;
442 		auto strC2 = str;
443 		assert(!strC.empty);
444 		assert(!strC2.empty);
445 
446 		r.popFront();
447 		str.popFront();
448 		assert(str.front == r.front, to!string(r.front));
449 		assert(s != r);
450 
451 		r.popBack();
452 		str.popBack();
453 		assert(str.back == r.back, to!string(r.front));
454 		assert(str.front == r.front, to!string(r.front));
455 
456 		assert(!strC.empty);
457 		assert(!s.empty);
458 		while(!strC.empty && !s.empty) {
459 			assert(strC.front == s.front);
460 			assert(strC.back == s.back);
461 			assert(strC.length == s.length);
462 			for(size_t i = 0; i < strC.length; ++i) {
463 				assert(strC[i] == s[i]);
464 			}
465 
466 			strC.popFront();
467 			s.popFront();
468 		}
469 
470 		assert(strC.empty);
471 		assert(s.empty);
472 
473 		assert(!strC2.empty);
474 		assert(!t.empty);
475 		while(!strC2.empty && !t.empty) {
476 			assert(strC2.front == t.front);
477 			assert(strC2.back == t.back);
478 			assert(strC2.length == t.length);
479 			for(size_t i = 0; i < strC2.length; ++i) {
480 				assert(strC2[i] == t[i]);
481 			}
482 
483 			strC2.popFront();
484 			t.popFront();
485 		}
486 
487 		assert(strC2.empty);
488 		assert(t.empty);
489 	}
490 }
491 
492 @safe pure unittest {
493 	import std.traits : TypeTuple;
494 
495 	foreach(Buf; TypeTuple!(1,2,3,4,8,9,12,16,20,21)) {
496 		testFunc!(char,Buf)();
497 		testFunc!(wchar,Buf)();
498 		testFunc!(dchar,Buf)();
499 	}
500 }