strex: added `detectUrl()`
[iv.d.git] / dynstring.d
blobdf238a1b81d44c58dffdd6bc200763bf14a7cbf2
1 /*
2 * simple malloced refcounted dynamic strings
4 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
5 * Understanding is not required. Only obedience.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, version 3 of the License ONLY.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 module iv.dynstring;
21 private import iv.atomic;
22 private import iv.strex;
23 private import iv.utfutil;
25 //version = dynstring_debug;
26 //version = dynstring_debug_clear;
27 //version = dynstring_more_asserts;
30 // ////////////////////////////////////////////////////////////////////////// //
31 alias dynstr = dynstring;
33 public struct dynstring {
34 private:
35 static struct Data {
36 uint rc = void;
37 uint used = void;
38 uint alloted = void;
39 uint dummy = void;
40 // string data follows
42 inout(char)* ptr () inout pure nothrow @trusted @nogc { pragma(inline, true); return (cast(inout(char)*)&this)+Data.sizeof; }
44 static assert(Data.sizeof == 16, "invalid `dynstring.Data` size");
46 private:
47 usize udata = 0;
48 uint ofs = 0, len = 0; // for slices
50 nothrow @trusted @nogc:
51 private:
52 inout(Data)* datap () inout pure { pragma(inline, true); return cast(Data*)udata; }
54 void outofmem () const {
55 import core.exception : onOutOfMemoryErrorNoGC;
56 version(dynstring_test) assert(0, "out of memory");
57 onOutOfMemoryErrorNoGC();
60 void incRef () const {
61 pragma(inline, true);
62 if (udata) {
63 atomicFetchAdd((cast(Data*)udata).rc, 1);
64 version(dynstring_debug) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "incRef: %08x; rc=%u; used=%u; alloted=%u; len=%u; ofs=%u\n", cast(uint)udata, datap.rc, datap.used, datap.alloted, len, ofs); }
68 void decRef () {
69 pragma(inline, true);
70 if (udata) {
71 version(dynstring_debug) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "decRef: %08x; rc=%u; used=%u; alloted=%u; len=%u; ofs=%u\n", cast(uint)udata, datap.rc, datap.used, datap.alloted, len, ofs); }
72 if (atomicFetchSub((cast(Data*)udata).rc, 1) == 1) {
73 version(dynstring_debug) {
74 import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "freeData: %08x; used=%u; alloted=%u\n", cast(uint)udata, (cast(Data*)udata).used, (cast(Data*)udata).alloted);
76 import core.stdc.stdlib : free;
77 version(dynstring_debug_clear) import core.stdc.string : memset;
78 version(dynstring_debug_clear) assert((cast(Data*)udata).used <= (cast(Data*)udata).alloted);
79 version(dynstring_debug_clear) memset((cast(Data*)udata), 0, Data.sizeof);
80 free((cast(Data*)udata));
82 udata = 0;
83 ofs = len = 0;
87 private:
88 bool isMyData (const(void)* ptr) const pure {
89 pragma(inline, true);
90 if (ptr is null || !udata) return false;
91 immutable usize cc = cast(usize)ptr;
92 return (cc >= udata+Data.sizeof && cc < udata+Data.sizeof+datap.alloted);
95 static uint alignSize(bool overalloc) (uint sz) pure {
96 pragma(inline, true);
97 static if (overalloc) {
98 if (sz <= 256) return (sz|0x1fU)+1U;
99 else if (sz <= 1024) return (sz|0x3fU)+1U;
100 else if (sz <= 8192) return (sz|0x1ffU)+1U;
101 else if (sz <= 16384) return (sz|0x3ffU)+1U;
102 else if (sz <= 32768) return (sz|0xfffU)+1U;
103 else return (sz|0x1fffU)+1U;
104 } else {
105 return (sz|0x1fU)+1U;
109 // make sure that we have a room for at least `sz` chars
110 void ensureRoomFor(bool overalloc) (uint sz) {
111 if (sz == 0) return;
112 if (sz > 0x3fff_ffffU) outofmem();
113 // if empty, allocate new storage and data
114 if (!udata) {
115 import core.stdc.stdlib : malloc;
116 version(dynstring_more_asserts) assert(len == 0);
117 sz = alignSize!overalloc(sz);
118 Data* xdp = cast(Data*)malloc(Data.sizeof+sz);
119 if (xdp is null) outofmem();
120 xdp.rc = 1;
121 xdp.used = 0;
122 xdp.alloted = sz;
123 xdp.dummy = 0;
124 udata = cast(usize)xdp;
125 ofs = len = 0; // just in case
126 version(dynstring_debug) {
127 import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "NEWSTR000: udata=0x%08x; rc=%u; ofs=%u; len=%u; used=%u; alloted=%u; sz=%u\n",
128 cast(uint)udata, (udata ? datap.rc : 0), ofs, len, (udata ? datap.used : 0), (udata ? datap.alloted : 0), sz);
130 return;
132 // if shared, allocate completely new data and storage
133 Data* dp = datap;
134 if (atomicLoad(dp.rc) != 1) {
135 version(dynstring_debug) {
136 import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "NEWSTR100: udata=0x%08x; rc=%u; ofs=%u; len=%u; used=%u; alloted=%u; sz=%u\n",
137 cast(uint)udata, (udata ? datap.rc : 0), ofs, len, (udata ? datap.used : 0), (udata ? datap.alloted : 0), sz);
139 import core.stdc.stdlib : malloc;
140 if (len > 0x3fff_ffffU || uint.max-len <= sz || len+sz > 0x3fff_ffffU) outofmem();
141 sz = alignSize!overalloc(len+sz);
142 version(dynstring_more_asserts) assert(sz > len);
143 Data* dpnew = cast(Data*)malloc(Data.sizeof+sz);
144 if (dpnew is null) outofmem();
145 dpnew.rc = 1;
146 dpnew.used = len;
147 dpnew.alloted = sz;
148 dpnew.dummy = 0;
149 if (len) dpnew.ptr[0..len] = dp.ptr[ofs..ofs+len];
150 decRef();
151 udata = cast(usize)dpnew;
152 ofs = 0; // always reset by `decRef()`, but meh...
153 len = dpnew.used; // it was reset by `decRef()`, so restore it
154 version(dynstring_debug) {
155 import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "NEWSTR101: udata=0x%08x; rc=%u; ofs=%u; len=%u; used=%u; alloted=%u; sz=%u\n",
156 cast(uint)udata, (udata ? datap.rc : 0), ofs, len, (udata ? datap.used : 0), (udata ? datap.alloted : 0), sz);
158 return;
160 // if unshared slice, normalise storage
161 if (ofs) {
162 version(dynstring_debug) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "strOFS: udata=0x%08x; rc=%u; ofs=%u; len=%u; used=%u; alloted=%u\n", cast(uint)dp, dp.rc, ofs, len, dp.used, dp.alloted); }
163 import core.stdc.string : memmove;
164 if (len) memmove(dp.ptr, dp.ptr+ofs, len);
165 ofs = 0;
167 if (len != dp.used) {
168 version(dynstring_debug) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "strLEN: udata=0x%08x (0x%08x); rc=%u; ofs=%u; len=%u; used=%u; alloted=%u\n", cast(uint)dp, udata, dp.rc, ofs, len, dp.used, dp.alloted); }
169 version(dynstring_more_asserts) assert(len <= dp.used);
170 dp.used = len;
172 // not shared, not slice, expand storage
173 version(dynstring_more_asserts) assert(ofs == 0);
174 version(dynstring_more_asserts) assert(len == dp.used);
175 if (len > 0x3fff_ffffU || uint.max-len <= sz || len+sz > 0x3fff_ffffU) outofmem();
176 sz = alignSize!overalloc(len+sz);
177 version(dynstring_more_asserts) assert(sz > len);
178 if (sz > dp.alloted) {
179 import core.stdc.stdlib : realloc;
180 Data* np = cast(Data*)realloc(dp, Data.sizeof+sz);
181 if (np is null) outofmem();
182 np.alloted = sz;
183 udata = cast(usize)np;
184 version(dynstring_debug) {
185 import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "NEWSTR200: udata=0x%08x; rc=%u; ofs=%u; len=%u; used=%u; alloted=%u; sz=%u\n",
186 cast(uint)udata, (udata ? datap.rc : 0), ofs, len, (udata ? datap.used : 0), (udata ? datap.alloted : 0), sz);
191 enum MixinEnsureUniqueBuf(string bname) = `
192 dynstring tmpstr;
193 if (isMyData(`~bname~`.ptr)) {
194 version(dynstring_debug) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "MYDATA!: udata=0x%08x; rc=%u; ofs=%u; len=%u; used=%u; alloted=%u; ptr=0x%08x\n",
195 cast(uint)udata, datap.rc, ofs, len, datap.used, datap.alloted, cast(uint)`~bname~`.ptr); }
196 tmpstr.set(`~bname~`);
197 `~bname~` = cast(typeof(`~bname~`))tmpstr.getData();
198 version(dynstring_more_asserts) assert(!isMyData(`~bname~`.ptr));
202 public:
203 alias getData this;
205 public:
206 this() (in auto ref dynstring s) { pragma(inline, true); if (s.len) { udata = s.udata; ofs = s.ofs; len = s.len; incRef(); } }
208 this (const(char)[] s) {
209 if (s.length) {
210 ensureRoomFor!false(s.length);
211 datap.ptr[0..s.length] = s[];
212 len = datap.used = cast(uint)s.length;
213 //ofs = 0;
217 this (in char ch) { ensureRoomFor!false(1); datap.ptr[0] = ch; len = datap.used = 1; ofs = 0; }
219 this (this) { pragma(inline, true); if (udata) incRef(); }
220 ~this () { pragma(inline, true); if (udata) decRef(); }
222 void clear () { pragma(inline, true); decRef(); }
224 void makeUnique(bool compress=false) () {
225 if (!udata) { ofs = len = 0; return; }
226 if (!len) { decRef(); return; }
227 Data* dp = datap;
228 // non-shared?
229 if (atomicLoad(dp.rc) == 1) {
230 // normalise storage
231 if (ofs) {
232 import core.stdc.string : memmove;
233 if (len) memmove(dp.ptr, dp.ptr+ofs, len);
234 ofs = 0;
236 if (len != dp.used) { version(dynstring_more_asserts) assert(len <= dp.used); dp.used = len; }
237 static if (compress) {
238 if ((dp.used|0x1fU)+1U < dp.alloted) {
239 // realloc
240 import core.stdc.stdlib : realloc;
241 immutable uint sz = (dp.used|0x1fU)+1U;
242 Data* np = cast(Data*)realloc(dp, Data.sizeof+sz);
243 if (np !is null) {
244 np.alloted = sz;
245 udata = cast(usize)np;
246 version(dynstring_debug) {
247 import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UNIQUERE: udata=0x%08x; rc=%u; ofs=%u; len=%u; used=%u; alloted=%u\n",
248 cast(uint)udata, (udata ? datap.rc : 0), ofs, len, (udata ? datap.used : 0), (udata ? datap.alloted : 0));
253 return;
255 // allocate new storage
256 dynstring tmpstr;
257 tmpstr.set(getData());
258 decRef();
259 udata = tmpstr.udata;
260 ofs = tmpstr.ofs;
261 len = tmpstr.len;
262 tmpstr.udata = 0;
263 tmpstr.ofs = tmpstr.len = 0;
266 //WARNING! MAKE SURE THAT YOU KNOW WHAT YOU'RE DOING!
267 char *makeUniquePointer () { pragma(inline, true); makeUnique(); return (len ? datap.ptr : null); }
269 bool isSlice () const pure { pragma(inline, true); return !!ofs; }
270 bool isShared () const pure { pragma(inline, true); return (udata && atomicLoad(datap.rc) != 1); }
271 uint capacity () const pure { pragma(inline, true); return (udata ? datap.alloted : 0); }
273 void reserve(bool overalloc=false) (uint newsz) {
274 pragma(inline, true);
275 if (newsz && newsz < 0x3fff_ffffU && newsz > len) ensureRoomFor!overalloc(newsz-len);
278 void append(bool overalloc=true) (const(void)[] buf) {
279 if (buf.length == 0) return;
280 if (buf.length > cast(usize)0x3fff_ffff) outofmem();
281 //if (udata && cast(usize)0x3000_0000-datap.used < buf.length) outofmem();
282 version(dynstring_debug) {
283 import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "APPEND000: udata=0x%08x; rc=%u; ofs=%u; len=%u; used=%u; alloted=%u; ptr=0x%08x; buflen=%u\n",
284 cast(uint)udata, (udata ? datap.rc : 0), ofs, len, (udata ? datap.used : 0), (udata ? datap.alloted : 0), cast(uint)buf.ptr, cast(uint)buf.length);
286 mixin(MixinEnsureUniqueBuf!"buf");
287 version(dynstring_debug) {
288 import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "APPEND001: udata=0x%08x; rc=%u; ofs=%u; len=%u; used=%u; alloted=%u; ptr=0x%08x; buflen=%u\n",
289 cast(uint)udata, (udata ? datap.rc : 0), ofs, len, (udata ? datap.used : 0), (udata ? datap.alloted : 0), cast(uint)buf.ptr, cast(uint)buf.length);
291 ensureRoomFor!overalloc(buf.length);
292 version(dynstring_debug) {
293 import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "APPEND002: udata=0x%08x; rc=%u; ofs=%u; len=%u; used=%u; alloted=%u; ptr=0x%08x; buflen=%u\n",
294 cast(uint)udata, (udata ? datap.rc : 0), ofs, len, (udata ? datap.used : 0), (udata ? datap.alloted : 0), cast(uint)buf.ptr, cast(uint)buf.length);
296 version(dynstring_more_asserts) assert(ofs == 0);
297 Data* data = datap;
298 version(dynstring_more_asserts) assert(data !is null && data.used+buf.length <= data.alloted);
299 version(dynstring_more_asserts) assert(len == datap.used);
300 data.ptr[len..len+buf.length] = cast(const(char)[])buf;
301 data.used += cast(uint)buf.length;
302 len += cast(uint)buf.length;
303 version(dynstring_more_asserts) assert(len <= data.used);
305 void append(bool overalloc=true) (in char ch) { pragma(inline, true); append!overalloc((&ch)[0..1]); }
307 void set (const(void)[] buf) {
308 if (buf.length > cast(usize)0x3fff_ffff) outofmem();
309 if (buf.length == 0) { decRef(); return; }
310 if (udata) {
311 if (isMyData(buf.ptr)) {
312 // do not copy data if it is not necessary
313 if (ofs == 0 && buf.ptr == datap.ptr && buf.length <= datap.used) {
314 if (len != cast(uint)buf.length) {
315 len = cast(uint)buf.length;
316 if (atomicLoad(datap.rc) == 1) datap.used = len;
318 return;
320 dynstring tmpstr;
321 tmpstr.set(buf);
322 decRef();
323 udata = tmpstr.udata;
324 ofs = tmpstr.ofs;
325 len = tmpstr.len;
326 tmpstr.udata = 0;
327 tmpstr.ofs = tmpstr.len = 0;
328 return;
330 decRef();
332 version(dynstring_more_asserts) assert(udata == 0 && ofs == 0 && len == 0);
333 ensureRoomFor!false(buf.length);
334 datap.ptr[0..buf.length] = cast(const(char)[])buf;
335 len = datap.used = cast(uint)buf.length;
337 void set (in char ch) { pragma(inline, true); set((&ch)[0..1]); }
339 uint length () const pure { pragma(inline, true); return len; }
341 void length (in uint newlen) {
342 pragma(inline, true);
343 if (newlen < len) {
344 len = newlen;
345 } else if (newlen != len) {
346 ensureRoomFor!false(newlen-len);
347 version(dynstring_more_asserts) assert(len < newlen);
348 version(dynstring_more_asserts) assert(ofs == 0);
349 version(dynstring_more_asserts) assert(newlen <= datap.alloted);
350 datap.ptr[len..newlen] = 0;
351 len = datap.used = newlen;
355 const(char)[] getData () const pure { pragma(inline, true); return (len ? datap.ptr[ofs..ofs+len] : null); }
356 const(ubyte)[] getBytes () const pure { pragma(inline, true); return cast(const(ubyte)[])(len ? datap.ptr[ofs..ofs+len] : null); }
358 void opAssign() (const(char)[] s) { pragma(inline, true); set(s); }
359 void opAssign() (in char ch) { pragma(inline, true); set(ch); }
361 void opAssign() (in auto ref dynstring s) {
362 pragma(inline, true);
363 if (s.udata != udata) {
364 if (s.len) {
365 s.incRef();
366 decRef();
367 udata = s.udata;
368 ofs = s.ofs;
369 len = s.len;
370 } else {
371 decRef();
373 } else {
374 // same data, so RC is already valid
375 ofs = s.ofs;
376 len = s.len;
380 void opOpAssign(string op:"~") (const(char)[] s) { pragma(inline, true); if (s.length) append(s); }
381 void opOpAssign(string op:"~") (in char ch) { pragma(inline, true); append(ch); }
382 void opOpAssign(string op:"~") (in auto ref dynstring s) { pragma(inline, true); if (s.len) append(s.getData()); }
384 const(char)[] opCast(T=const(char)[]) () const pure { pragma(inline, true); return getData(); }
385 const(ubyte)[] opCast(T=const(ubyte)[]) () const pure { pragma(inline, true); return getBytes(); }
387 bool opCast(T=bool) () const pure { pragma(inline, true); return (len != 0); }
389 bool opEqual (const(char)[] other) const pure { pragma(inline, true); return (other.length == len && other[] == getBytes()[]); }
390 bool opEqual() (in auto ref dynstring other) const pure { pragma(inline, true); return (other.len == len && other.getBytes()[] == getBytes()[]); }
392 int opCmp (const(char)[] other) const pure {
393 if (len == 0) return (other.length ? -1 : 0);
394 if (other.length == 0) return 1;
395 if (len == other.length && datap.ptr+ofs == other.ptr) return 0;
396 import core.stdc.string : memcmp;
397 immutable int cres = memcmp(datap.ptr+ofs, other.ptr, (len <= other.length ? len : other.length));
398 if (cres != 0) return (cres < 0 ? -1 : +1);
399 return (len < other.length ? -1 : len > other.length ? +1 : 0);
402 int opCmp() (in auto ref dynstring other) const { pragma(inline, true); return opCmp(other.getData); }
404 dynstring opBinary(string op:"~") (in auto ref dynstring other) const {
405 pragma(inline, true);
406 dynstring res;
407 if (!len) {
408 res.set(other.getData);
409 } else if (!other.len) {
410 res.set(getData);
411 } else {
412 if (uint.max-len <= other.len) outofmem();
413 res.ensureRoomFor!false(len+other.len);
414 res.datap.ptr[0..len] = getData();
415 res.datap.ptr[len..len+other.len] = other.getData();
416 version(dynstring_more_asserts) assert(res.ofs == 0);
417 res.datap.used = res.len = len+other.len;
419 return res;
422 dynstring opBinary(string op:"~") (const(char)[] other) const {
423 pragma(inline, true);
424 dynstring res;
425 if (!len) {
426 res.set(other);
427 } else if (!other.length) {
428 res.set(getData);
429 } else {
430 if (uint.max-len <= other.length) outofmem();
431 res.ensureRoomFor!false(len+cast(uint)other.length);
432 res.datap.ptr[0..len] = getData();
433 res.datap.ptr[len..len+other.length] = other;
434 version(dynstring_more_asserts) assert(res.ofs == 0);
435 res.datap.used = res.len = len+cast(uint)other.length;
437 return res;
440 char opIndex (in uint pos) const pure {
441 pragma(inline, true);
442 //if (pos >= len) assert(0, "dynstring index out of bounds"); return datap.ptr[ofs+pos];
443 return (pos < len ? datap.ptr[ofs+pos] : 0);
446 void opIndexAssign (in char ch, in uint pos) {
447 pragma(inline, true);
448 if (pos >= len) assert(0, "dynstring index out of bounds");
449 makeUnique();
450 version(dynstring_more_asserts) assert(pos < len);
451 datap.ptr[ofs+pos] = ch;
454 dynstring opSlice (in uint lo, in uint hi) const {
456 if (lo > hi) assert(0, "dynstring slice index out of bounds (0)");
457 if (lo > len || hi > len) assert(0, "dynstring slice index out of bounds (1)");
459 dynstring res;
460 if (lo > hi || lo > len || hi > len) return res;
461 if (lo < hi) {
462 incRef();
463 res.udata = udata;
464 res.ofs = ofs+lo;
465 res.len = hi-lo;
467 return res;
470 uint opDollar () const pure { pragma(inline, true); return len; }
472 void appendQEncoded (const(void)[] ss) {
473 static bool isSpecial (immutable char ch) pure nothrow @safe @nogc {
474 return
475 ch < ' ' ||
476 ch >= 127 ||
477 ch == '\'' ||
478 ch == '`' ||
479 ch == '"' ||
480 ch == '\\' ||
481 ch == '@';
484 if (ss.length == 0) return;
485 const(char)[] s = cast(const(char)[])ss;
487 static immutable string hexd = "0123456789abcdef";
489 bool needWork = (s[0] == '=' || s[0] == '?');
490 if (!needWork) foreach (char ch; s) if (isSpecial(ch)) { needWork = true; break; }
492 if (!needWork) {
493 append(s);
494 } else {
495 mixin(MixinEnsureUniqueBuf!"ss");
496 append("=?UTF-8?Q?"); // quoted printable
497 foreach (char ch; s) {
498 if (ch <= ' ') ch = '_';
499 if (!isSpecial(ch) && ch != '=' && ch != '?') {
500 append(ch);
501 } else {
502 append("=");
503 append(hexd[(cast(ubyte)ch)>>4]);
504 append(hexd[(cast(ubyte)ch)&0x0f]);
507 append("?=");
511 void appendB64Encoded (const(void)[] buf, uint maxlinelen=76) {
512 static immutable string b64alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
513 ubyte[3] bts = void;
514 uint btspos = 0;
515 uint linelen = 0;
517 void putB64Char (immutable char ch) nothrow @trusted @nogc {
518 if (maxlinelen && linelen >= maxlinelen) { append("\r\n"); linelen = 0; }
519 append(ch);
520 ++linelen;
523 void encodeChunk () nothrow @trusted @nogc {
524 if (btspos == 0) return;
525 putB64Char(b64alphabet.ptr[(bts.ptr[0]&0xfc)>>2]);
526 if (btspos == 1) {
527 putB64Char(b64alphabet.ptr[(bts.ptr[0]&0x03)<<4]);
528 /*static if (padding)*/ { putB64Char('='); putB64Char('='); }
529 } else {
530 // 2 or more
531 putB64Char(b64alphabet.ptr[((bts.ptr[0]&0x03)<<4)|((bts.ptr[1]&0xf0)>>4)]);
532 if (btspos == 2) {
533 putB64Char(b64alphabet.ptr[(bts.ptr[1]&0x0f)<<2]);
534 /*static if (padding)*/ putB64Char('=');
535 } else {
536 // 3 bytes
537 putB64Char(b64alphabet.ptr[((bts.ptr[1]&0x0f)<<2)|((bts.ptr[2]&0xc0)>>6)]);
538 putB64Char(b64alphabet.ptr[bts.ptr[2]&0x3f]);
541 btspos = 0;
544 mixin(MixinEnsureUniqueBuf!"buf");
546 foreach (immutable ubyte ib; (cast(const(ubyte)[])buf)[]) {
547 bts.ptr[btspos++] = ib;
548 if (btspos == 3) encodeChunk();
550 if (btspos != 0) encodeChunk();
552 if (maxlinelen) append("\r\n");
555 void appendNum(T) (in T v) if (__traits(isIntegral, T)) {
556 import core.stdc.stdio : snprintf;
557 static if (T.sizeof <= 4) {
558 char[32] buf = void;
559 static if (__traits(isUnsigned, T)) {
560 auto len = snprintf(buf.ptr, buf.length, "%u", cast(uint)v);
561 } else {
562 auto len = snprintf(buf.ptr, buf.length, "%d", cast(int)v);
564 append(buf[0..len]);
565 } else {
566 char[128] buf = void;
567 static if (__traits(isUnsigned, T)) {
568 auto len = snprintf(buf.ptr, buf.length, "%llu", cast(ulong)v);
569 } else {
570 auto len = snprintf(buf.ptr, buf.length, "%lld", cast(long)v);
572 append(buf[0..len]);
576 void removeASCIICtrls (in char repch=' ') {
577 static bool isCtrl (in char ch) pure nothrow @safe @nogc {
578 pragma(inline, true);
579 return (ch < 32 && ch != '\t' && ch != '\n');
582 bool needWork = false;
583 foreach (immutable char ch; getData) if (isCtrl(ch)) { needWork = true; break; }
584 if (!needWork) return;
586 makeUnique();
587 char[] buf = cast(char[])datap.ptr[0..datap.used];
588 foreach (ref char ch; buf) if (isCtrl(ch)) ch = repch;
591 // only for ASCII
592 void lowerInPlace () {
593 bool needWork = false;
594 foreach (immutable char ch; getData) if (ch >= 'A' && ch <= 'Z') { needWork = true; break; }
595 if (!needWork) return;
597 makeUnique();
598 char[] buf = cast(char[])datap.ptr[0..datap.used];
599 foreach (ref char ch; buf) if (ch >= 'A' && ch <= 'Z') ch += 32;
602 // only for ASCII
603 void upperInPlace () {
604 bool needWork = false;
605 foreach (immutable char ch; getData) if (ch >= 'a' && ch <= 'z') { needWork = true; break; }
606 if (!needWork) return;
608 makeUnique();
609 char[] buf = cast(char[])datap.ptr[0..datap.used];
610 foreach (ref char ch; buf) if (ch >= 'a' && ch <= 'z') ch -= 32;
613 dynstring xstripright () const {
614 dynstring res;
615 if (len == 0) return res;
616 if (opIndex(len-1) > ' ') { res = this; return res; }
617 const(char)[] dt = getData().xstripright;
618 res.set(dt);
619 return res;
622 dynstring xstripleft () const {
623 dynstring res;
624 if (len == 0) return res;
625 if (opIndex(0) > ' ') { res = this; return res; }
626 const(char)[] dt = getData().xstripleft;
627 res.set(dt);
628 return res;
631 dynstring xstrip () const {
632 dynstring res;
633 if (len == 0) return res;
634 if (opIndex(0) > ' ' && opIndex(len-1) > ' ') { res = this; return res; }
635 const(char)[] dt = getData().xstrip;
636 res.set(dt);
637 return res;
640 // this can never return `int.min` from conversion!
641 int toInt (int defval=int.min) const pure {
642 const(char)[] dt = getData().xstrip;
643 if (dt.length == 0) return defval;
644 bool neg = false;
645 if (dt[0] == '+') dt = dt[1..$];
646 else if (dt[0] == '-') { neg = true; dt = dt[1..$]; }
647 bool wasDigit = false;
648 int n = 0;
649 foreach (immutable char ch; dt) {
650 if (ch == '_' || ch == ' ' || ch == ',') continue;
651 immutable int dg = digitInBase(ch, 10);
652 if (dg < 0) return defval;
653 wasDigit = true;
654 n *= 10;
655 if (n < 0) return defval;
656 n += dg;
657 if (n < 0) return defval;
659 if (!wasDigit) return defval;
660 if (neg) n = -n;
661 return n;
664 public:
665 private static struct LineIteratorImpl {
666 dynstring s;
667 usize curridx;
669 nothrow @trusted @nogc:
670 this (dynstring src) { s = src; }
671 this (in ref LineIteratorImpl src) { s = src.s; curridx = src.curridx; }
673 @property bool empty () const pure { return (curridx >= s.length); }
675 @property auto save () { return LineIteratorImpl(s); }
677 @property const(char)[] front () const pure {
678 if (empty) return null;
679 const(char)[] ss = s.getData[curridx..$];
680 auto ep = ss.indexOf('\n');
681 if (ep >= 0) ss = ss[0..ep];
682 return ss;
685 void popFront () {
686 if (empty) return;
687 const(char)[] ss = s.getData;
688 auto ep = ss.indexOf('\n', curridx);
689 if (ep < 0) { curridx = s.length; return; }
690 curridx = ep+1;
694 auto byLine () { return LineIteratorImpl(this); }
696 public:
697 private static struct UniCharIteratorImpl {
698 dynstring s;
699 usize curridx;
701 nothrow @trusted @nogc:
702 this (dynstring src) { s = src; }
703 this (in ref UniCharIteratorImpl src) { s = src.s; curridx = src.curridx; }
705 @property bool empty () const pure { return (curridx >= s.length); }
707 @property auto save () { return UniCharIteratorImpl(s); }
709 @property dchar front () const {
710 if (empty) return Utf8DecoderFast.replacement;
711 Utf8DecoderFast dc;
712 usize pos = curridx;
713 while (pos < s.length) if (dc.decodeSafe(s[pos++])) return dc.codepoint;
714 return Utf8DecoderFast.replacement;
717 void popFront () {
718 if (empty) return;
719 Utf8DecoderFast dc;
720 while (curridx < s.length) if (dc.decodeSafe(s[curridx++])) break;
724 auto byUniChar () { return UniCharIteratorImpl(this); }
728 version(dynstring_test) {
729 import iv.dynstring;
730 import iv.vfs.io;
732 void main () {
733 dynstring tmp;
734 tmp = "abc";
735 writefln("tmp: udata=0x%08x; (%u) ofs=%u; len=%u; <%s>", tmp.udata, (tmp.udata ? tmp.datap.rc : 0), tmp.ofs, tmp.len, tmp.getData);
736 tmp ~= "d";
737 writefln("tmp: udata=0x%08x; (%u) ofs=%u; len=%u; <%s>", tmp.udata, (tmp.udata ? tmp.datap.rc : 0), tmp.ofs, tmp.len, tmp.getData);
738 tmp = tmp[1..3];
739 writefln("tmp: udata=0x%08x; (%u) ofs=%u; len=%u; <%s>", tmp.udata, (tmp.udata ? tmp.datap.rc : 0), tmp.ofs, tmp.len, tmp.getData);
740 tmp[1] = '!';
741 writefln("tmp: udata=0x%08x; (%u) ofs=%u; len=%u; <%s>", tmp.udata, (tmp.udata ? tmp.datap.rc : 0), tmp.ofs, tmp.len, tmp.getData);
742 tmp.makeUnique();
743 writefln("tmp: udata=0x%08x; (%u) ofs=%u; len=%u; <%s>", tmp.udata, (tmp.udata ? tmp.datap.rc : 0), tmp.ofs, tmp.len, tmp.getData);
745 tmp = "abcdefgh";
746 writefln("tmp: udata=0x%08x; (%u) ofs=%u; len=%u; <%s>", tmp.udata, (tmp.udata ? tmp.datap.rc : 0), tmp.ofs, tmp.len, tmp.getData);
747 tmp = tmp[1..$-1];
748 writefln("tmp: udata=0x%08x; (%u) ofs=%u; len=%u; <%s>", tmp.udata, (tmp.udata ? tmp.datap.rc : 0), tmp.ofs, tmp.len, tmp.getData);
749 tmp = tmp[1..$-1];
750 writefln("tmp: udata=0x%08x; (%u) ofs=%u; len=%u; <%s>", tmp.udata, (tmp.udata ? tmp.datap.rc : 0), tmp.ofs, tmp.len, tmp.getData);
751 writeln("tmp[2]='", tmp[2], "'");
753 tmp = " goo ";
754 tmp = tmp.xstripright;
755 writeln("tmp: <", tmp.getData, ">");
757 tmp = " goo ";
758 tmp = tmp.xstripleft;
759 writeln("tmp: <", tmp.getData, ">");
761 tmp = " goo ";
762 tmp = tmp.xstrip;
763 writeln("tmp: <", tmp.getData, ">");
765 tmp = ".Алиса.";
766 foreach (dchar dch; tmp.byUniChar) writefln("0x%04x", cast(uint)dch);