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 }