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 }