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 }+/