egra: do not record cells outside of the vertical canvas (framebuffer) in agg mini...
[iv.d.git] / dynstring.d
blob10a35d98546e747949cc30d3671e956ff47a5b3b
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;
24 //version = dynstring_debug;
25 //version = dynstring_debug_clear;
26 //version = dynstring_more_asserts;
29 // ////////////////////////////////////////////////////////////////////////// //
30 alias dynstr = dynstring;
32 public struct dynstring {
33 private:
34 static struct Data {
35 uint rc = void;
36 uint used = void;
37 uint alloted = void;
38 uint dummy = void;
39 // string data follows
41 inout(char)* ptr () inout pure nothrow @trusted @nogc { pragma(inline, true); return (cast(inout(char)*)&this)+Data.sizeof; }
43 static assert(Data.sizeof == 16, "invalid `dynstring.Data` size");
45 private:
46 usize udata = 0;
47 uint ofs = 0, len = 0; // for slices
49 nothrow @trusted @nogc:
50 private:
51 inout(Data)* datap () inout pure { pragma(inline, true); return cast(Data*)udata; }
53 void outofmem () const {
54 import core.exception : onOutOfMemoryErrorNoGC;
55 version(dynstring_test) assert(0, "out of memory");
56 onOutOfMemoryErrorNoGC();
59 void incRef () const {
60 pragma(inline, true);
61 if (udata) {
62 atomicFetchAdd((cast(Data*)udata).rc, 1);
63 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); }
67 void decRef () {
68 pragma(inline, true);
69 if (udata) {
70 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); }
71 if (atomicFetchSub((cast(Data*)udata).rc, 1) == 1) {
72 version(dynstring_debug) {
73 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);
75 import core.stdc.stdlib : free;
76 version(dynstring_debug_clear) import core.stdc.string : memset;
77 version(dynstring_debug_clear) assert((cast(Data*)udata).used <= (cast(Data*)udata).alloted);
78 version(dynstring_debug_clear) memset((cast(Data*)udata), 0, Data.sizeof);
79 free((cast(Data*)udata));
81 udata = 0;
82 ofs = len = 0;
86 private:
87 bool isMyData (const(void)* ptr) const pure {
88 pragma(inline, true);
89 if (ptr is null || !udata) return false;
90 immutable usize cc = cast(usize)ptr;
91 return (cc >= udata+Data.sizeof && cc < udata+Data.sizeof+datap.alloted);
94 static uint alignSize(bool overalloc) (uint sz) pure {
95 pragma(inline, true);
96 static if (overalloc) {
97 if (sz <= 256) return (sz|0x1fU)+1U;
98 else if (sz <= 1024) return (sz|0x3fU)+1U;
99 else if (sz <= 8192) return (sz|0x1ffU)+1U;
100 else if (sz <= 16384) return (sz|0x3ffU)+1U;
101 else if (sz <= 32768) return (sz|0xfffU)+1U;
102 else return (sz|0x1fffU)+1U;
103 } else {
104 return (sz|0x1fU)+1U;
108 // make sure that we have a room for at least `sz` chars
109 void ensureRoomFor(bool overalloc) (uint sz) {
110 if (sz == 0) return;
111 if (sz > 0x3fff_ffffU) outofmem();
112 // if empty, allocate new storage and data
113 if (!udata) {
114 import core.stdc.stdlib : malloc;
115 version(dynstring_more_asserts) assert(len == 0);
116 sz = alignSize!overalloc(sz);
117 Data* xdp = cast(Data*)malloc(Data.sizeof+sz);
118 if (xdp is null) outofmem();
119 xdp.rc = 1;
120 xdp.used = 0;
121 xdp.alloted = sz;
122 xdp.dummy = 0;
123 udata = cast(usize)xdp;
124 ofs = len = 0; // just in case
125 version(dynstring_debug) {
126 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",
127 cast(uint)udata, (udata ? datap.rc : 0), ofs, len, (udata ? datap.used : 0), (udata ? datap.alloted : 0), sz);
129 return;
131 // if shared, allocate completely new data and storage
132 Data* dp = datap;
133 if (atomicLoad(dp.rc) != 1) {
134 version(dynstring_debug) {
135 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",
136 cast(uint)udata, (udata ? datap.rc : 0), ofs, len, (udata ? datap.used : 0), (udata ? datap.alloted : 0), sz);
138 import core.stdc.stdlib : malloc;
139 if (len > 0x3fff_ffffU || uint.max-len <= sz || len+sz > 0x3fff_ffffU) outofmem();
140 sz = alignSize!overalloc(len+sz);
141 version(dynstring_more_asserts) assert(sz > len);
142 Data* dpnew = cast(Data*)malloc(Data.sizeof+sz);
143 if (dpnew is null) outofmem();
144 dpnew.rc = 1;
145 dpnew.used = len;
146 dpnew.alloted = sz;
147 dpnew.dummy = 0;
148 if (len) dpnew.ptr[0..len] = dp.ptr[ofs..ofs+len];
149 decRef();
150 udata = cast(usize)dpnew;
151 ofs = 0; // always reset by `decRef()`, but meh...
152 len = dpnew.used; // it was reset by `decRef()`, so restore it
153 version(dynstring_debug) {
154 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",
155 cast(uint)udata, (udata ? datap.rc : 0), ofs, len, (udata ? datap.used : 0), (udata ? datap.alloted : 0), sz);
157 return;
159 // if unshared slice, normalise storage
160 if (ofs) {
161 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); }
162 import core.stdc.string : memmove;
163 if (len) memmove(dp.ptr, dp.ptr+ofs, len);
164 ofs = 0;
166 if (len != dp.used) {
167 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); }
168 version(dynstring_more_asserts) assert(len <= dp.used);
169 dp.used = len;
171 // not shared, not slice, expand storage
172 version(dynstring_more_asserts) assert(ofs == 0);
173 version(dynstring_more_asserts) assert(len == dp.used);
174 if (len > 0x3fff_ffffU || uint.max-len <= sz || len+sz > 0x3fff_ffffU) outofmem();
175 sz = alignSize!overalloc(len+sz);
176 version(dynstring_more_asserts) assert(sz > len);
177 if (sz > dp.alloted) {
178 import core.stdc.stdlib : realloc;
179 Data* np = cast(Data*)realloc(dp, Data.sizeof+sz);
180 if (np is null) outofmem();
181 np.alloted = sz;
182 udata = cast(usize)np;
183 version(dynstring_debug) {
184 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",
185 cast(uint)udata, (udata ? datap.rc : 0), ofs, len, (udata ? datap.used : 0), (udata ? datap.alloted : 0), sz);
190 enum MixinEnsureUniqueBuf(string bname) = `
191 dynstring tmpstr;
192 if (isMyData(`~bname~`.ptr)) {
193 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",
194 cast(uint)udata, datap.rc, ofs, len, datap.used, datap.alloted, cast(uint)`~bname~`.ptr); }
195 tmpstr.set(`~bname~`);
196 `~bname~` = cast(typeof(`~bname~`))tmpstr.getData();
197 version(dynstring_more_asserts) assert(!isMyData(`~bname~`.ptr));
201 public:
202 alias getData this;
204 public:
205 this() (in auto ref dynstring s) { pragma(inline, true); if (s.len) { udata = s.udata; ofs = s.ofs; len = s.len; incRef(); } }
207 this (const(char)[] s) {
208 if (s.length) {
209 ensureRoomFor!false(s.length);
210 datap.ptr[0..s.length] = s[];
211 len = datap.used = cast(uint)s.length;
212 //ofs = 0;
216 this (in char ch) { ensureRoomFor!false(1); datap.ptr[0] = ch; len = datap.used = 1; ofs = 0; }
218 this (this) { pragma(inline, true); if (udata) incRef(); }
219 ~this () { pragma(inline, true); if (udata) decRef(); }
221 void clear () { pragma(inline, true); decRef(); }
223 void makeUnique(bool compress=false) () {
224 if (!udata) { ofs = len = 0; return; }
225 if (!len) { decRef(); return; }
226 Data* dp = datap;
227 // non-shared?
228 if (atomicLoad(dp.rc) == 1) {
229 // normalise storage
230 if (ofs) {
231 import core.stdc.string : memmove;
232 if (len) memmove(dp.ptr, dp.ptr+ofs, len);
233 ofs = 0;
235 if (len != dp.used) { version(dynstring_more_asserts) assert(len <= dp.used); dp.used = len; }
236 static if (compress) {
237 if ((dp.used|0x1fU)+1U < dp.alloted) {
238 // realloc
239 import core.stdc.stdlib : realloc;
240 immutable uint sz = (dp.used|0x1fU)+1U;
241 Data* np = cast(Data*)realloc(dp, Data.sizeof+sz);
242 if (np !is null) {
243 np.alloted = sz;
244 udata = cast(usize)np;
245 version(dynstring_debug) {
246 import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UNIQUERE: udata=0x%08x; rc=%u; ofs=%u; len=%u; used=%u; alloted=%u\n",
247 cast(uint)udata, (udata ? datap.rc : 0), ofs, len, (udata ? datap.used : 0), (udata ? datap.alloted : 0));
252 return;
254 // allocate new storage
255 dynstring tmpstr;
256 tmpstr.set(getData());
257 decRef();
258 udata = tmpstr.udata;
259 ofs = tmpstr.ofs;
260 len = tmpstr.len;
261 tmpstr.udata = 0;
262 tmpstr.ofs = tmpstr.len = 0;
265 //WARNING! MAKE SURE THAT YOU KNOW WHAT YOU'RE DOING!
266 char *makeUniquePointer () { pragma(inline, true); makeUnique(); return (len ? datap.ptr : null); }
268 bool isSlice () const pure { pragma(inline, true); return !!ofs; }
269 bool isShared () const pure { pragma(inline, true); return (udata && atomicLoad(datap.rc) != 1); }
270 uint capacity () const pure { pragma(inline, true); return (udata ? datap.alloted : 0); }
272 void reserve(bool overalloc=false) (uint newsz) {
273 pragma(inline, true);
274 if (newsz && newsz < 0x3fff_ffffU && newsz > len) ensureRoomFor!overalloc(newsz-len);
277 void append(bool overalloc=true) (const(void)[] buf) {
278 if (buf.length == 0) return;
279 if (buf.length > cast(usize)0x3fff_ffff) outofmem();
280 //if (udata && cast(usize)0x3000_0000-datap.used < buf.length) outofmem();
281 version(dynstring_debug) {
282 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",
283 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);
285 mixin(MixinEnsureUniqueBuf!"buf");
286 version(dynstring_debug) {
287 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",
288 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);
290 ensureRoomFor!overalloc(buf.length);
291 version(dynstring_debug) {
292 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",
293 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);
295 version(dynstring_more_asserts) assert(ofs == 0);
296 Data* data = datap;
297 version(dynstring_more_asserts) assert(data !is null && data.used+buf.length <= data.alloted);
298 version(dynstring_more_asserts) assert(len == datap.used);
299 data.ptr[len..len+buf.length] = cast(const(char)[])buf;
300 data.used += cast(uint)buf.length;
301 len += cast(uint)buf.length;
302 version(dynstring_more_asserts) assert(len <= data.used);
304 void append(bool overalloc=true) (in char ch) { pragma(inline, true); append!overalloc((&ch)[0..1]); }
306 void set (const(void)[] buf) {
307 if (buf.length > cast(usize)0x3fff_ffff) outofmem();
308 if (buf.length == 0) { decRef(); return; }
309 if (udata) {
310 if (isMyData(buf.ptr)) {
311 // do not copy data if it is not necessary
312 if (ofs == 0 && buf.ptr == datap.ptr && buf.length <= datap.used) {
313 if (len != cast(uint)buf.length) {
314 len = cast(uint)buf.length;
315 if (atomicLoad(datap.rc) == 1) datap.used = len;
317 return;
319 dynstring tmpstr;
320 tmpstr.set(buf);
321 decRef();
322 udata = tmpstr.udata;
323 ofs = tmpstr.ofs;
324 len = tmpstr.len;
325 tmpstr.udata = 0;
326 tmpstr.ofs = tmpstr.len = 0;
327 return;
329 decRef();
331 version(dynstring_more_asserts) assert(udata == 0 && ofs == 0 && len == 0);
332 ensureRoomFor!false(buf.length);
333 datap.ptr[0..buf.length] = cast(const(char)[])buf;
334 len = datap.used = cast(uint)buf.length;
336 void set (in char ch) { pragma(inline, true); set((&ch)[0..1]); }
338 uint length () const pure { pragma(inline, true); return len; }
340 void length (in uint newlen) {
341 pragma(inline, true);
342 if (newlen < len) {
343 len = newlen;
344 } else if (newlen != len) {
345 ensureRoomFor!false(newlen-len);
346 version(dynstring_more_asserts) assert(len < newlen);
347 version(dynstring_more_asserts) assert(ofs == 0);
348 version(dynstring_more_asserts) assert(newlen <= datap.alloted);
349 datap.ptr[len..newlen] = 0;
350 len = datap.used = newlen;
354 const(char)[] getData () const pure { pragma(inline, true); return (len ? datap.ptr[ofs..ofs+len] : null); }
355 const(ubyte)[] getBytes () const pure { pragma(inline, true); return cast(const(ubyte)[])(len ? datap.ptr[ofs..ofs+len] : null); }
357 void opAssign() (const(char)[] s) { pragma(inline, true); set(s); }
358 void opAssign() (in char ch) { pragma(inline, true); set(ch); }
360 void opAssign() (in auto ref dynstring s) {
361 pragma(inline, true);
362 if (s.udata != udata) {
363 if (s.len) {
364 s.incRef();
365 decRef();
366 udata = s.udata;
367 ofs = s.ofs;
368 len = s.len;
369 } else {
370 decRef();
372 } else {
373 // same data, so RC is already valid
374 ofs = s.ofs;
375 len = s.len;
379 void opOpAssign(string op:"~") (const(char)[] s) { pragma(inline, true); if (s.length) append(s); }
380 void opOpAssign(string op:"~") (in char ch) { pragma(inline, true); append(ch); }
381 void opOpAssign(string op:"~") (in auto ref dynstring s) { pragma(inline, true); if (s.len) append(s.getData()); }
383 const(char)[] opCast(T=const(char)[]) () const pure { pragma(inline, true); return getData(); }
384 const(ubyte)[] opCast(T=const(ubyte)[]) () const pure { pragma(inline, true); return getBytes(); }
386 bool opCast(T=bool) () const pure { pragma(inline, true); return (len != 0); }
388 bool opEqual (const(char)[] other) const pure { pragma(inline, true); return (other.length == len && other[] == getBytes()[]); }
389 bool opEqual() (in auto ref dynstring other) const pure { pragma(inline, true); return (other.len == len && other.getBytes()[] == getBytes()[]); }
391 int opCmp (const(char)[] other) const pure {
392 if (len == 0) return (other.length ? -1 : 0);
393 if (other.length == 0) return 1;
394 if (len == other.length && datap.ptr+ofs == other.ptr) return 0;
395 import core.stdc.string : memcmp;
396 immutable int cres = memcmp(datap.ptr+ofs, other.ptr, (len <= other.length ? len : other.length));
397 if (cres != 0) return (cres < 0 ? -1 : +1);
398 return (len < other.length ? -1 : len > other.length ? +1 : 0);
401 int opCmp() (in auto ref dynstring other) const { pragma(inline, true); return opCmp(other.getData); }
403 dynstring opBinary(string op:"~") (in auto ref dynstring other) const {
404 pragma(inline, true);
405 dynstring res;
406 if (!len) {
407 res.set(other.getData);
408 } else if (!other.len) {
409 res.set(getData);
410 } else {
411 if (uint.max-len <= other.len) outofmem();
412 res.ensureRoomFor!false(len+other.len);
413 res.datap.ptr[0..len] = getData();
414 res.datap.ptr[len..len+other.len] = other.getData();
415 version(dynstring_more_asserts) assert(res.ofs == 0);
416 res.datap.used = res.len = len+other.len;
418 return res;
421 dynstring opBinary(string op:"~") (const(char)[] other) const {
422 pragma(inline, true);
423 dynstring res;
424 if (!len) {
425 res.set(other);
426 } else if (!other.length) {
427 res.set(getData);
428 } else {
429 if (uint.max-len <= other.length) outofmem();
430 res.ensureRoomFor!false(len+cast(uint)other.length);
431 res.datap.ptr[0..len] = getData();
432 res.datap.ptr[len..len+other.length] = other;
433 version(dynstring_more_asserts) assert(res.ofs == 0);
434 res.datap.used = res.len = len+cast(uint)other.length;
436 return res;
439 char opIndex (in uint pos) const pure { pragma(inline, true); if (pos >= len) assert(0, "dynstring index out of bounds"); return datap.ptr[ofs+pos]; }
440 void opIndexAssign (in char ch, in uint pos) {
441 pragma(inline, true);
442 if (pos >= len) assert(0, "dynstring index out of bounds");
443 makeUnique();
444 version(dynstring_more_asserts) assert(pos < len);
445 datap.ptr[ofs+pos] = ch;
448 dynstring opSlice (in uint lo, in uint hi) const {
449 if (lo > hi) assert(0, "dynstring slice index out of bounds (0)");
450 if (lo > len || hi > len) assert(0, "dynstring slice index out of bounds (1)");
451 dynstring res;
452 if (lo < hi) {
453 incRef();
454 res.udata = udata;
455 res.ofs = ofs+lo;
456 res.len = hi-lo;
458 return res;
461 uint opDollar () const pure { pragma(inline, true); return len; }
463 void appendQEncoded (const(void)[] ss) {
464 static bool isSpecial (immutable char ch) pure nothrow @safe @nogc {
465 return
466 ch < ' ' ||
467 ch >= 127 ||
468 ch == '\'' ||
469 ch == '`' ||
470 ch == '"' ||
471 ch == '\\' ||
472 ch == '@';
475 if (ss.length == 0) return;
476 const(char)[] s = cast(const(char)[])ss;
478 static immutable string hexd = "0123456789abcdef";
480 bool needWork = (s[0] == '=' || s[0] == '?');
481 if (!needWork) foreach (char ch; s) if (isSpecial(ch)) { needWork = true; break; }
483 if (!needWork) {
484 append(s);
485 } else {
486 mixin(MixinEnsureUniqueBuf!"ss");
487 append("=?UTF-8?Q?"); // quoted printable
488 foreach (char ch; s) {
489 if (ch <= ' ') ch = '_';
490 if (!isSpecial(ch) && ch != '=' && ch != '?') {
491 append(ch);
492 } else {
493 append("=");
494 append(hexd[(cast(ubyte)ch)>>4]);
495 append(hexd[(cast(ubyte)ch)&0x0f]);
498 append("?=");
502 void appendB64Encoded (const(void)[] buf, uint maxlinelen=76) {
503 static immutable string b64alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
504 ubyte[3] bts = void;
505 uint btspos = 0;
506 uint linelen = 0;
508 void putB64Char (immutable char ch) nothrow @trusted @nogc {
509 if (maxlinelen && linelen >= maxlinelen) { append("\r\n"); linelen = 0; }
510 append(ch);
511 ++linelen;
514 void encodeChunk () nothrow @trusted @nogc {
515 if (btspos == 0) return;
516 putB64Char(b64alphabet.ptr[(bts.ptr[0]&0xfc)>>2]);
517 if (btspos == 1) {
518 putB64Char(b64alphabet.ptr[(bts.ptr[0]&0x03)<<4]);
519 /*static if (padding)*/ { putB64Char('='); putB64Char('='); }
520 } else {
521 // 2 or more
522 putB64Char(b64alphabet.ptr[((bts.ptr[0]&0x03)<<4)|((bts.ptr[1]&0xf0)>>4)]);
523 if (btspos == 2) {
524 putB64Char(b64alphabet.ptr[(bts.ptr[1]&0x0f)<<2]);
525 /*static if (padding)*/ putB64Char('=');
526 } else {
527 // 3 bytes
528 putB64Char(b64alphabet.ptr[((bts.ptr[1]&0x0f)<<2)|((bts.ptr[2]&0xc0)>>6)]);
529 putB64Char(b64alphabet.ptr[bts.ptr[2]&0x3f]);
532 btspos = 0;
535 mixin(MixinEnsureUniqueBuf!"buf");
537 foreach (immutable ubyte ib; (cast(const(ubyte)[])buf)[]) {
538 bts.ptr[btspos++] = ib;
539 if (btspos == 3) encodeChunk();
541 if (btspos != 0) encodeChunk();
543 if (maxlinelen) append("\r\n");
546 void appendNum(T) (in T v) if (__traits(isIntegral, T)) {
547 import core.stdc.stdio : snprintf;
548 static if (T.sizeof <= 4) {
549 char[32] buf = void;
550 static if (__traits(isUnsigned, T)) {
551 auto len = snprintf(buf.ptr, buf.length, "%u", cast(uint)v);
552 } else {
553 auto len = snprintf(buf.ptr, buf.length, "%d", cast(int)v);
555 append(buf[0..len]);
556 } else {
557 char[128] buf = void;
558 static if (__traits(isUnsigned, T)) {
559 auto len = snprintf(buf.ptr, buf.length, "%llu", cast(ulong)v);
560 } else {
561 auto len = snprintf(buf.ptr, buf.length, "%lld", cast(long)v);
563 append(buf[0..len]);
567 void removeASCIICtrls (in char repch=' ') {
568 static bool isCtrl (in char ch) pure nothrow @safe @nogc {
569 pragma(inline, true);
570 return (ch < 32 && ch != '\t' && ch != '\n');
573 bool needWork = false;
574 foreach (immutable char ch; getData) if (isCtrl(ch)) { needWork = true; break; }
575 if (!needWork) return;
577 makeUnique();
578 char[] buf = cast(char[])datap.ptr[0..datap.used];
579 foreach (ref char ch; buf) if (isCtrl(ch)) ch = repch;
582 // only for ASCII
583 void lowerInPlace () {
584 bool needWork = false;
585 foreach (immutable char ch; getData) if (ch >= 'A' && ch <= 'Z') { needWork = true; break; }
586 if (!needWork) return;
588 makeUnique();
589 char[] buf = cast(char[])datap.ptr[0..datap.used];
590 foreach (ref char ch; buf) if (ch >= 'A' && ch <= 'Z') ch += 32;
593 // only for ASCII
594 void upperInPlace () {
595 bool needWork = false;
596 foreach (immutable char ch; getData) if (ch >= 'a' && ch <= 'z') { needWork = true; break; }
597 if (!needWork) return;
599 makeUnique();
600 char[] buf = cast(char[])datap.ptr[0..datap.used];
601 foreach (ref char ch; buf) if (ch >= 'a' && ch <= 'z') ch -= 32;
604 dynstring xstripright () const {
605 dynstring res;
606 if (len == 0) return res;
607 if (opIndex(len-1) > ' ') { res = this; return res; }
608 const(char)[] dt = getData().xstripright;
609 res.set(dt);
610 return res;
613 dynstring xstripleft () const {
614 dynstring res;
615 if (len == 0) return res;
616 if (opIndex(0) > ' ') { res = this; return res; }
617 const(char)[] dt = getData().xstripleft;
618 res.set(dt);
619 return res;
622 dynstring xstrip () const {
623 dynstring res;
624 if (len == 0) return res;
625 if (opIndex(0) > ' ' && opIndex(len-1) > ' ') { res = this; return res; }
626 const(char)[] dt = getData().xstrip;
627 res.set(dt);
628 return res;
631 // this can never return `int.min` from conversion!
632 int toInt (int defval=int.min) const pure {
633 const(char)[] dt = getData().xstrip;
634 if (dt.length == 0) return defval;
635 bool neg = false;
636 if (dt[0] == '+') dt = dt[1..$];
637 else if (dt[0] == '-') { neg = true; dt = dt[1..$]; }
638 bool wasDigit = false;
639 int n = 0;
640 foreach (immutable char ch; dt) {
641 if (ch == '_' || ch == ' ' || ch == ',') continue;
642 immutable int dg = digitInBase(ch, 10);
643 if (dg < 0) return defval;
644 wasDigit = true;
645 n *= 10;
646 if (n < 0) return defval;
647 n += dg;
648 if (n < 0) return defval;
650 if (!wasDigit) return defval;
651 if (neg) n = -n;
652 return n;
655 public:
656 private static struct LineIteratorImpl {
657 dynstring s;
658 usize curridx;
659 nothrow @trusted @nogc:
660 this (dynstring src) { s = src; }
662 @property bool empty () const pure { return (curridx >= s.length); }
664 @property auto save () { LineIteratorImpl res = LineIteratorImpl(s); res.curridx = curridx; return res; }
666 @property const(char)[] front () const pure {
667 if (empty) return null;
668 const(char)[] ss = s.getData[curridx..$];
669 auto ep = ss.indexOf('\n');
670 if (ep >= 0) ss = ss[0..ep];
671 return ss;
674 void popFront () {
675 if (empty) return;
676 const(char)[] ss = s.getData;
677 auto ep = ss.indexOf('\n', curridx);
678 if (ep < 0) { curridx = s.length; return; }
679 curridx = ep+1;
683 auto byLine () { return LineIteratorImpl(this); }
687 version(dynstring_test) {
688 import iv.dynstring;
689 import iv.vfs.io;
691 void main () {
692 dynstring tmp;
693 tmp = "abc";
694 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);
695 tmp ~= "d";
696 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);
697 tmp = tmp[1..3];
698 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);
699 tmp[1] = '!';
700 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);
701 tmp.makeUnique();
702 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);
704 tmp = "abcdefgh";
705 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);
706 tmp = tmp[1..$-1];
707 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);
708 tmp = tmp[1..$-1];
709 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);
710 writeln("tmp[2]='", tmp[2], "'");
712 tmp = " goo ";
713 tmp = tmp.xstripright;
714 writeln("tmp: <", tmp.getData, ">");
716 tmp = " goo ";
717 tmp = tmp.xstripleft;
718 writeln("tmp: <", tmp.getData, ">");
720 tmp = " goo ";
721 tmp = tmp.xstrip;
722 writeln("tmp: <", tmp.getData, ">");