1 /** 2 * An expandable buffer in which you can write text or binary data. 3 * 4 * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved 5 * Authors: Walter Bright, https://www.digitalmars.com 6 * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/root/outbuffer.d, root/_outbuffer.d) 8 * Documentation: https://dlang.org/phobos/dmd_root_outbuffer.html 9 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/root/outbuffer.d 10 */ 11 12 module dmd.common.outbuffer; 13 14 import core.stdc.stdarg; 15 import core.stdc.stdio; 16 import core.stdc.string; 17 import core.stdc.stdlib; 18 19 nothrow: 20 21 // In theory these functions should also restore errno, but we don't care because 22 // we abort application on error anyway. 23 extern (C) private pure @system @nogc nothrow 24 { 25 pragma(mangle, "malloc") void* pureMalloc(size_t); 26 pragma(mangle, "realloc") void* pureRealloc(void* ptr, size_t size); 27 pragma(mangle, "free") void pureFree(void* ptr); 28 } 29 30 debug 31 { 32 debug = stomp; // flush out dangling pointer problems by stomping on unused memory 33 } 34 35 /** 36 `OutBuffer` is a write-only output stream of untyped data. It is backed up by 37 a contiguous array or a memory-mapped file. 38 */ 39 struct OutBuffer 40 { 41 import dmd.common.file : FileMapping, touchFile, writeFile; 42 43 // IMPORTANT: PLEASE KEEP STATE AND DESTRUCTOR IN SYNC WITH DEFINITION IN ./outbuffer.h. 44 // state { 45 private ubyte[] data; 46 private size_t offset; 47 private bool notlinehead; 48 /// File mapping, if any. Use a pointer for ABI compatibility with the C++ counterpart. 49 /// If the pointer is non-null the store is a memory-mapped file, otherwise the store is RAM. 50 private FileMapping!ubyte* fileMapping; 51 /// Whether to indent 52 bool doindent; 53 /// Whether to indent by 4 spaces or by tabs; 54 bool spaces; 55 /// Current indent level 56 int level; 57 // state } 58 59 nothrow: 60 61 /** 62 Construct given size. 63 */ thisOutBuffer64 this(size_t initialSize) nothrow 65 { 66 reserve(initialSize); 67 } 68 69 /** 70 Construct from filename. Will map the file into memory (or create it anew 71 if necessary) and start writing at the beginning of it. 72 73 Params: 74 filename = zero-terminated name of file to map into memory 75 */ thisOutBuffer76 @trusted this(const(char)* filename) 77 { 78 FileMapping!ubyte model; 79 fileMapping = cast(FileMapping!ubyte*) malloc(model.sizeof); 80 memcpy(fileMapping, &model, model.sizeof); 81 fileMapping.__ctor(filename); 82 //fileMapping = new FileMapping!ubyte(filename); 83 data = (*fileMapping)[]; 84 } 85 86 /** 87 Frees resources associated. 88 */ dtorOutBuffer89 extern (C++) void dtor() pure nothrow @trusted 90 { 91 if (fileMapping) 92 { 93 if (fileMapping.active) 94 fileMapping.close(); 95 } 96 else 97 { 98 debug (stomp) memset(data.ptr, 0xFF, data.length); 99 pureFree(data.ptr); 100 } 101 } 102 103 /** 104 Frees resources associated automatically. 105 */ ~thisOutBuffer106 extern (C++) ~this() pure nothrow @trusted 107 { 108 dtor(); 109 } 110 111 /// For porting with ease from dmd.backend.outbuf.Outbuffer bufOutBuffer112 ubyte* buf() nothrow { 113 return data.ptr; 114 } 115 116 /// For porting with ease from dmd.backend.outbuf.Outbuffer bufptrOutBuffer117 ubyte** bufptr() nothrow { 118 static struct Array { size_t length; ubyte* ptr; } 119 auto a = cast(Array*) &data; 120 assert(a.length == data.length && a.ptr == data.ptr); 121 return &a.ptr; 122 } 123 lengthOutBuffer124 extern (C++) size_t length() const pure @nogc @safe nothrow { return offset; } 125 126 /********************** 127 * Transfer ownership of the allocated data to the caller. 128 * Returns: 129 * pointer to the allocated data 130 */ extractDataOutBuffer131 extern (C++) char* extractData() pure nothrow @nogc @trusted 132 { 133 char* p = cast(char*)data.ptr; 134 data = null; 135 offset = 0; 136 return p; 137 } 138 139 /** 140 Releases all resources associated with `this` and resets it as an empty 141 memory buffer. The config variables `notlinehead`, `doindent` etc. are 142 not changed. 143 */ destroyOutBuffer144 extern (C++) void destroy() pure nothrow @trusted 145 { 146 dtor(); 147 fileMapping = null; 148 data = null; 149 offset = 0; 150 } 151 152 /** 153 Reserves `nbytes` bytes of additional memory (or file space) in advance. 154 The resulting capacity is at least the previous length plus `nbytes`. 155 156 Params: 157 nbytes = the number of additional bytes to reserve 158 */ reserveOutBuffer159 extern (C++) void reserve(size_t nbytes) pure nothrow 160 { 161 //debug (stomp) printf("OutBuffer::reserve: size = %lld, offset = %lld, nbytes = %lld\n", data.length, offset, nbytes); 162 const minSize = offset + nbytes; 163 if (data.length >= minSize) 164 return; 165 166 /* Increase by factor of 1.5; round up to 16 bytes. 167 * The odd formulation is so it will map onto single x86 LEA instruction. 168 */ 169 const size = ((minSize * 3 + 30) / 2) & ~15; 170 171 if (fileMapping && fileMapping.active) 172 { 173 fileMapping.resize(size); 174 data = (*fileMapping)[]; 175 } 176 else 177 { 178 debug (stomp) 179 { 180 auto p = cast(ubyte*) pureMalloc(size); 181 p || assert(0, "OutBuffer: out of memory."); 182 memcpy(p, data.ptr, offset); 183 memset(data.ptr, 0xFF, data.length); // stomp old location 184 pureFree(data.ptr); 185 memset(p + offset, 0xff, size - offset); // stomp unused data 186 } 187 else 188 { 189 auto p = cast(ubyte*) pureRealloc(data.ptr, size); 190 p || assert(0, "OutBuffer: out of memory."); 191 memset(p + offset + nbytes, 0xff, size - offset - nbytes); 192 } 193 data = p[0 .. size]; 194 } 195 } 196 197 /************************ 198 * Shrink the size of the data to `size`. 199 * Params: 200 * size = new size of data, must be <= `.length` 201 */ setsizeOutBuffer202 extern (C++) void setsize(size_t size) pure nothrow @nogc @safe 203 { 204 assert(size <= data.length); 205 offset = size; 206 } 207 resetOutBuffer208 extern (C++) void reset() pure nothrow @nogc @safe 209 { 210 offset = 0; 211 } 212 indentOutBuffer213 private void indent() pure nothrow 214 { 215 if (level) 216 { 217 const indentLevel = spaces ? level * 4 : level; 218 reserve(indentLevel); 219 data[offset .. offset + indentLevel] = (spaces ? ' ' : '\t'); 220 offset += indentLevel; 221 } 222 notlinehead = true; 223 } 224 225 // Write an array to the buffer, no reserve check 226 @trusted nothrow writenOutBuffer227 void writen(const void *b, size_t len) 228 { 229 memcpy(data.ptr + offset, b, len); 230 offset += len; 231 } 232 writeOutBuffer233 extern (C++) void write(const(void)* data, size_t nbytes) pure nothrow 234 { 235 write(data[0 .. nbytes]); 236 } 237 writeOutBuffer238 void write(const(void)[] buf) pure nothrow 239 { 240 if (doindent && !notlinehead) 241 indent(); 242 reserve(buf.length); 243 memcpy(this.data.ptr + offset, buf.ptr, buf.length); 244 offset += buf.length; 245 } 246 247 /** 248 * Writes a 16 bit value, no reserve check. 249 */ 250 @trusted nothrow write16nOutBuffer251 void write16n(int v) 252 { 253 auto x = cast(ushort) v; 254 data[offset] = x & 0x00FF; 255 data[offset + 1] = x >> 8u; 256 offset += 2; 257 } 258 259 /** 260 * Writes a 16 bit value. 261 */ write16OutBuffer262 void write16(int v) nothrow 263 { 264 auto u = cast(ushort) v; 265 write(&u, u.sizeof); 266 } 267 268 /** 269 * Writes a 32 bit int. 270 */ write32OutBuffer271 void write32(int v) nothrow @trusted 272 { 273 write(&v, v.sizeof); 274 } 275 276 /** 277 * Writes a 64 bit int. 278 */ write64OutBuffer279 @trusted void write64(long v) nothrow 280 { 281 write(&v, v.sizeof); 282 } 283 284 /// NOT zero-terminated writestringOutBuffer285 extern (C++) void writestring(const(char)* s) pure nothrow 286 { 287 if (!s) 288 return; 289 import core.stdc.string : strlen; 290 write(s[0 .. strlen(s)]); 291 } 292 293 /// ditto writestringOutBuffer294 void writestring(const(char)[] s) pure nothrow 295 { 296 write(s); 297 } 298 299 /// ditto writestringOutBuffer300 void writestring(string s) pure nothrow 301 { 302 write(s); 303 } 304 305 /// NOT zero-terminated, followed by newline writestringlnOutBuffer306 void writestringln(const(char)[] s) pure nothrow 307 { 308 writestring(s); 309 writenl(); 310 } 311 312 // Zero-terminated writeStringOutBuffer313 void writeString(const(char)* s) pure nothrow @trusted 314 { 315 write(s[0 .. strlen(s)+1]); 316 } 317 318 /// ditto writeStringOutBuffer319 void writeString(const(char)[] s) pure nothrow 320 { 321 write(s); 322 writeByte(0); 323 } 324 325 /// ditto writeStringOutBuffer326 void writeString(string s) pure nothrow 327 { 328 writeString(cast(const(char)[])(s)); 329 } 330 prependstringOutBuffer331 extern (C++) void prependstring(const(char)* string) pure nothrow 332 { 333 size_t len = strlen(string); 334 reserve(len); 335 memmove(data.ptr + len, data.ptr, offset); 336 memcpy(data.ptr, string, len); 337 offset += len; 338 } 339 340 /// write newline writenlOutBuffer341 extern (C++) void writenl() pure nothrow 342 { 343 version (Windows) 344 { 345 writeword(0x0A0D); // newline is CR,LF on Microsoft OS's 346 } 347 else 348 { 349 writeByte('\n'); 350 } 351 if (doindent) 352 notlinehead = false; 353 } 354 355 // Write n zeros; return pointer to start of zeros 356 @trusted writezerosOutBuffer357 void *writezeros(size_t n) nothrow 358 { 359 reserve(n); 360 auto result = memset(data.ptr + offset, 0, n); 361 offset += n; 362 return result; 363 } 364 365 // Position buffer to accept the specified number of bytes at offset 366 @trusted positionOutBuffer367 void position(size_t where, size_t nbytes) nothrow 368 { 369 if (where + nbytes > data.length) 370 { 371 reserve(where + nbytes - offset); 372 } 373 offset = where; 374 375 debug assert(offset + nbytes <= data.length); 376 } 377 378 /** 379 * Writes an 8 bit byte, no reserve check. 380 */ 381 extern (C++) @trusted nothrow writeBytenOutBuffer382 void writeByten(int b) 383 { 384 this.data[offset++] = cast(ubyte) b; 385 } 386 writeByteOutBuffer387 extern (C++) void writeByte(uint b) pure nothrow 388 { 389 if (doindent && !notlinehead && b != '\n') 390 indent(); 391 reserve(1); 392 this.data[offset] = cast(ubyte)b; 393 offset++; 394 } 395 writeUTF8OutBuffer396 extern (C++) void writeUTF8(uint b) pure nothrow 397 { 398 reserve(6); 399 if (b <= 0x7F) 400 { 401 this.data[offset] = cast(ubyte)b; 402 offset++; 403 } 404 else if (b <= 0x7FF) 405 { 406 this.data[offset + 0] = cast(ubyte)((b >> 6) | 0xC0); 407 this.data[offset + 1] = cast(ubyte)((b & 0x3F) | 0x80); 408 offset += 2; 409 } 410 else if (b <= 0xFFFF) 411 { 412 this.data[offset + 0] = cast(ubyte)((b >> 12) | 0xE0); 413 this.data[offset + 1] = cast(ubyte)(((b >> 6) & 0x3F) | 0x80); 414 this.data[offset + 2] = cast(ubyte)((b & 0x3F) | 0x80); 415 offset += 3; 416 } 417 else if (b <= 0x1FFFFF) 418 { 419 this.data[offset + 0] = cast(ubyte)((b >> 18) | 0xF0); 420 this.data[offset + 1] = cast(ubyte)(((b >> 12) & 0x3F) | 0x80); 421 this.data[offset + 2] = cast(ubyte)(((b >> 6) & 0x3F) | 0x80); 422 this.data[offset + 3] = cast(ubyte)((b & 0x3F) | 0x80); 423 offset += 4; 424 } 425 else 426 assert(0); 427 } 428 prependbyteOutBuffer429 extern (C++) void prependbyte(uint b) pure nothrow 430 { 431 reserve(1); 432 memmove(data.ptr + 1, data.ptr, offset); 433 data[0] = cast(ubyte)b; 434 offset++; 435 } 436 writewcharOutBuffer437 extern (C++) void writewchar(uint w) pure nothrow 438 { 439 version (Windows) 440 { 441 writeword(w); 442 } 443 else 444 { 445 write4(w); 446 } 447 } 448 writewordOutBuffer449 extern (C++) void writeword(uint w) pure nothrow 450 { 451 version (Windows) 452 { 453 uint newline = 0x0A0D; 454 } 455 else 456 { 457 uint newline = '\n'; 458 } 459 if (doindent && !notlinehead && w != newline) 460 indent(); 461 462 reserve(2); 463 *cast(ushort*)(this.data.ptr + offset) = cast(ushort)w; 464 offset += 2; 465 } 466 writeUTF16OutBuffer467 extern (C++) void writeUTF16(uint w) pure nothrow 468 { 469 reserve(4); 470 if (w <= 0xFFFF) 471 { 472 *cast(ushort*)(this.data.ptr + offset) = cast(ushort)w; 473 offset += 2; 474 } 475 else if (w <= 0x10FFFF) 476 { 477 *cast(ushort*)(this.data.ptr + offset) = cast(ushort)((w >> 10) + 0xD7C0); 478 *cast(ushort*)(this.data.ptr + offset + 2) = cast(ushort)((w & 0x3FF) | 0xDC00); 479 offset += 4; 480 } 481 else 482 assert(0); 483 } 484 write4OutBuffer485 extern (C++) void write4(uint w) pure nothrow 486 { 487 version (Windows) 488 { 489 bool notnewline = w != 0x000A000D; 490 } 491 else 492 { 493 bool notnewline = true; 494 } 495 if (doindent && !notlinehead && notnewline) 496 indent(); 497 reserve(4); 498 *cast(uint*)(this.data.ptr + offset) = w; 499 offset += 4; 500 } 501 writeOutBuffer502 extern (C++) void write(const OutBuffer* buf) pure nothrow 503 { 504 if (buf) 505 { 506 reserve(buf.offset); 507 memcpy(data.ptr + offset, buf.data.ptr, buf.offset); 508 offset += buf.offset; 509 } 510 } 511 fill0OutBuffer512 extern (C++) void fill0(size_t nbytes) pure nothrow 513 { 514 reserve(nbytes); 515 memset(data.ptr + offset, 0, nbytes); 516 offset += nbytes; 517 } 518 519 /** 520 * Allocate space, but leave it uninitialized. 521 * Params: 522 * nbytes = amount to allocate 523 * Returns: 524 * slice of the allocated space to be filled in 525 */ allocateOutBuffer526 extern (D) char[] allocate(size_t nbytes) pure nothrow 527 { 528 reserve(nbytes); 529 offset += nbytes; 530 return cast(char[])data[offset - nbytes .. offset]; 531 } 532 vprintfOutBuffer533 extern (C++) void vprintf(const(char)* format, va_list args) nothrow 534 { 535 int count; 536 if (doindent && !notlinehead) 537 indent(); 538 uint psize = 128; 539 for (;;) 540 { 541 reserve(psize); 542 va_list va; 543 va_copy(va, args); 544 /* 545 The functions vprintf(), vfprintf(), vsprintf(), vsnprintf() 546 are equivalent to the functions printf(), fprintf(), sprintf(), 547 snprintf(), respectively, except that they are called with a 548 va_list instead of a variable number of arguments. These 549 functions do not call the va_end macro. Consequently, the value 550 of ap is undefined after the call. The application should call 551 va_end(ap) itself afterwards. 552 */ 553 count = vsnprintf(cast(char*)data.ptr + offset, psize, format, va); 554 va_end(va); 555 if (count == -1) // snn.lib and older libcmt.lib return -1 if buffer too small 556 psize *= 2; 557 else if (count >= psize) 558 psize = count + 1; 559 else 560 break; 561 } 562 offset += count; 563 // if (mem.isGCEnabled) 564 memset(data.ptr + offset, 0xff, psize - count); 565 } 566 567 static if (__VERSION__ < 2092) 568 { printfOutBuffer569 extern (C++) void printf(const(char)* format, ...) nothrow 570 { 571 va_list ap; 572 va_start(ap, format); 573 vprintf(format, ap); 574 va_end(ap); 575 } 576 } 577 else 578 { pragmaOutBuffer579 pragma(printf) extern (C++) void printf(const(char)* format, ...) nothrow 580 { 581 va_list ap; 582 va_start(ap, format); 583 vprintf(format, ap); 584 va_end(ap); 585 } 586 } 587 588 /************************************** 589 * Convert `u` to a string and append it to the buffer. 590 * Params: 591 * u = integral value to append 592 */ printOutBuffer593 extern (C++) void print(ulong u) pure nothrow 594 { 595 UnsignedStringBuf buf = void; 596 writestring(unsignedToTempString(u, buf)); 597 } 598 bracketOutBuffer599 extern (C++) void bracket(char left, char right) pure nothrow 600 { 601 reserve(2); 602 memmove(data.ptr + 1, data.ptr, offset); 603 data[0] = left; 604 data[offset + 1] = right; 605 offset += 2; 606 } 607 608 /****************** 609 * Insert left at i, and right at j. 610 * Return index just past right. 611 */ bracketOutBuffer612 extern (C++) size_t bracket(size_t i, const(char)* left, size_t j, const(char)* right) pure nothrow 613 { 614 size_t leftlen = strlen(left); 615 size_t rightlen = strlen(right); 616 reserve(leftlen + rightlen); 617 insert(i, left, leftlen); 618 insert(j + leftlen, right, rightlen); 619 return j + leftlen + rightlen; 620 } 621 spreadOutBuffer622 extern (C++) void spread(size_t offset, size_t nbytes) pure nothrow 623 { 624 reserve(nbytes); 625 memmove(data.ptr + offset + nbytes, data.ptr + offset, this.offset - offset); 626 this.offset += nbytes; 627 } 628 629 /**************************************** 630 * Returns: offset + nbytes 631 */ insertOutBuffer632 extern (C++) size_t insert(size_t offset, const(void)* p, size_t nbytes) pure nothrow 633 { 634 spread(offset, nbytes); 635 memmove(data.ptr + offset, p, nbytes); 636 return offset + nbytes; 637 } 638 insertOutBuffer639 size_t insert(size_t offset, const(char)[] s) pure nothrow 640 { 641 return insert(offset, s.ptr, s.length); 642 } 643 removeOutBuffer644 extern (C++) void remove(size_t offset, size_t nbytes) pure nothrow @nogc 645 { 646 memmove(data.ptr + offset, data.ptr + offset + nbytes, this.offset - (offset + nbytes)); 647 this.offset -= nbytes; 648 } 649 650 /** 651 * Returns: 652 * a non-owning const slice of the buffer contents 653 */ opSliceOutBuffer654 extern (D) const(char)[] opSlice() const pure nothrow @nogc @safe 655 { 656 return cast(const(char)[])data[0 .. offset]; 657 } 658 opSliceOutBuffer659 extern (D) const(char)[] opSlice(size_t lwr, size_t upr) const pure nothrow @nogc @safe 660 { 661 return cast(const(char)[])data[lwr .. upr]; 662 } 663 opIndexOutBuffer664 extern (D) char opIndex(size_t i) const pure nothrow @nogc @safe 665 { 666 return cast(char)data[i]; 667 } 668 669 alias opDollar = length; 670 671 /*********************************** 672 * Extract the data as a slice and take ownership of it. 673 * 674 * When `true` is passed as an argument, this function behaves 675 * like `dmd.utils.toDString(thisbuffer.extractChars())`. 676 * 677 * Params: 678 * nullTerminate = When `true`, the data will be `null` terminated. 679 * This is useful to call C functions or store 680 * the result in `Strings`. Defaults to `false`. 681 */ 682 extern (D) char[] extractSlice(bool nullTerminate = false) pure nothrow 683 { 684 const length = offset; 685 if (!nullTerminate) 686 return extractData()[0 .. length]; 687 // There's already a terminating `'\0'` 688 if (length && data[length - 1] == '\0') 689 return extractData()[0 .. length - 1]; 690 writeByte(0); 691 return extractData()[0 .. length]; 692 } 693 694 extern (D) byte[] extractUbyteSlice(bool nullTerminate = false) pure nothrow 695 { 696 return cast(byte[]) extractSlice(nullTerminate); 697 } 698 699 // Append terminating null if necessary and get view of internal buffer peekCharsOutBuffer700 extern (C++) char* peekChars() pure nothrow 701 { 702 if (!offset || data[offset - 1] != '\0') 703 { 704 writeByte(0); 705 offset--; // allow appending more 706 } 707 return cast(char*)data.ptr; 708 } 709 710 // Append terminating null if necessary and take ownership of data extractCharsOutBuffer711 extern (C++) char* extractChars() pure nothrow 712 { 713 if (!offset || data[offset - 1] != '\0') 714 writeByte(0); 715 return extractData(); 716 } 717 writesLEB128OutBuffer718 void writesLEB128(int value) pure nothrow 719 { 720 while (1) 721 { 722 ubyte b = value & 0x7F; 723 724 value >>= 7; // arithmetic right shift 725 if ((value == 0 && !(b & 0x40)) || 726 (value == -1 && (b & 0x40))) 727 { 728 writeByte(b); 729 break; 730 } 731 writeByte(b | 0x80); 732 } 733 } 734 writeuLEB128OutBuffer735 void writeuLEB128(uint value) pure nothrow 736 { 737 do 738 { 739 ubyte b = value & 0x7F; 740 741 value >>= 7; 742 if (value) 743 b |= 0x80; 744 writeByte(b); 745 } while (value); 746 } 747 748 /** 749 Destructively saves the contents of `this` to `filename`. As an 750 optimization, if the file already has identical contents with the buffer, 751 no copying is done. This is because on SSD drives reading is often much 752 faster than writing and because there's a high likelihood an identical 753 file is written during the build process. 754 755 Params: 756 filename = the name of the file to receive the contents 757 758 Returns: `true` iff the operation succeeded. 759 */ moveToFileOutBuffer760 extern(D) bool moveToFile(const char* filename) 761 { 762 bool result = true; 763 const bool identical = this[] == FileMapping!(const ubyte)(filename)[]; 764 765 if (fileMapping && fileMapping.active) 766 { 767 // Defer to corresponding functions in FileMapping. 768 if (identical) 769 { 770 result = fileMapping.discard(); 771 } 772 else 773 { 774 // Resize to fit to get rid of the slack bytes at the end 775 fileMapping.resize(offset); 776 result = fileMapping.moveToFile(filename); 777 } 778 // Can't call destroy() here because the file mapping is already closed. 779 data = null; 780 offset = 0; 781 } 782 else 783 { 784 if (!identical) 785 writeFile(filename, this[]); 786 destroy(); 787 } 788 789 return identical 790 ? result && touchFile(filename) 791 : result; 792 } 793 } 794 795 /****** copied from core.internal.string *************/ 796 797 private: 798 799 alias UnsignedStringBuf = char[20]; 800 801 char[] unsignedToTempString(ulong value, char[] buf, uint radix = 10) @safe pure nothrow @nogc 802 { 803 size_t i = buf.length; 804 do 805 { 806 if (value < radix) 807 { 808 ubyte x = cast(ubyte)value; 809 buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a'); 810 break; 811 } 812 else 813 { 814 ubyte x = cast(ubyte)(value % radix); 815 value /= radix; 816 buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a'); 817 } 818 } while (value); 819 return buf[i .. $]; 820 } 821 822 /************* unit tests **************************************************/ 823 824 unittest 825 { 826 OutBuffer buf; 827 buf.printf("betty"); 828 buf.insert(1, "xx".ptr, 2); 829 buf.insert(3, "yy"); 830 buf.remove(4, 1); 831 buf.bracket('(', ')'); 832 const char[] s = buf[]; 833 assert(s == "(bxxyetty)"); 834 buf.destroy(); 835 } 836 837 unittest 838 { 839 OutBuffer buf; 840 buf.writestring("abc".ptr); 841 buf.prependstring("def"); 842 buf.prependbyte('x'); 843 OutBuffer buf2; 844 buf2.writestring("mmm"); 845 buf.write(&buf2); 846 char[] s = buf.extractSlice(); 847 assert(s == "xdefabcmmm"); 848 } 849 850 unittest 851 { 852 OutBuffer buf; 853 buf.writeByte('a'); 854 char[] s = buf.extractSlice(); 855 assert(s == "a"); 856 857 buf.writeByte('b'); 858 char[] t = buf.extractSlice(); 859 assert(t == "b"); 860 } 861 862 unittest 863 { 864 OutBuffer buf; 865 char* p = buf.peekChars(); 866 assert(*p == 0); 867 868 buf.writeByte('s'); 869 char* q = buf.peekChars(); 870 assert(strcmp(q, "s") == 0); 871 } 872 873 unittest 874 { 875 char[10] buf; 876 char[] s = unsignedToTempString(278, buf[], 10); 877 assert(s == "278"); 878 879 s = unsignedToTempString(1, buf[], 10); 880 assert(s == "1"); 881 882 s = unsignedToTempString(8, buf[], 2); 883 assert(s == "1000"); 884 885 s = unsignedToTempString(29, buf[], 16); 886 assert(s == "1d"); 887 } 888 889 unittest 890 { 891 OutBuffer buf; 892 buf.writeUTF8(0x0000_0011); 893 buf.writeUTF8(0x0000_0111); 894 buf.writeUTF8(0x0000_1111); 895 buf.writeUTF8(0x0001_1111); 896 buf.writeUTF8(0x0010_0000); 897 assert(buf[] == "\x11\U00000111\U00001111\U00011111\U00100000"); 898 899 buf.reset(); 900 buf.writeUTF16(0x0000_0011); 901 buf.writeUTF16(0x0010_FFFF); 902 assert(buf[] == cast(string) "\u0011\U0010FFFF"w); 903 } 904 905 unittest 906 { 907 OutBuffer buf; 908 buf.doindent = true; 909 910 const(char)[] s = "abc"; 911 buf.writestring(s); 912 buf.level += 1; 913 buf.indent(); 914 buf.writestring("abs"); 915 916 assert(buf[] == "abc\tabs"); 917 918 buf.setsize(4); 919 assert(buf.length == 4); 920 } 921