1 /** 2 * An expandable buffer in which you can write text or binary data. 3 * 4 * Copyright: Copyright (C) 1999-2023 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 */ 64 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 */ 76 @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 */ 89 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 */ 106 extern (C++) ~this() pure nothrow @trusted 107 { 108 dtor(); 109 } 110 111 /// For porting with ease from dmd.backend.outbuf.Outbuffer 112 ubyte* buf() nothrow @system { 113 return data.ptr; 114 } 115 116 /// For porting with ease from dmd.backend.outbuf.Outbuffer 117 ubyte** bufptr() nothrow @system { 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 124 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 */ 131 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 */ 144 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 */ 159 extern (C++) void reserve(size_t nbytes) pure nothrow @trusted 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 */ 202 extern (C++) void setsize(size_t size) pure nothrow @nogc @safe 203 { 204 assert(size <= data.length); 205 offset = size; 206 } 207 208 extern (C++) void reset() pure nothrow @nogc @safe 209 { 210 offset = 0; 211 } 212 213 private void indent() pure nothrow @safe 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 @system nothrow 227 void writen(const void *b, size_t len) 228 { 229 memcpy(data.ptr + offset, b, len); 230 offset += len; 231 } 232 233 extern (C++) void write(const(void)* data, size_t nbytes) pure nothrow @system 234 { 235 write(data[0 .. nbytes]); 236 } 237 238 void write(scope const(void)[] buf) pure nothrow @trusted 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 251 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 */ 262 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 */ 271 void write32(int v) nothrow @trusted 272 { 273 write(&v, v.sizeof); 274 } 275 276 /** 277 * Writes a 64 bit int. 278 */ 279 @trusted void write64(long v) nothrow 280 { 281 write(&v, v.sizeof); 282 } 283 284 /// NOT zero-terminated 285 extern (C++) void writestring(const(char)* s) pure nothrow @system 286 { 287 if (!s) 288 return; 289 import core.stdc.string : strlen; 290 write(s[0 .. strlen(s)]); 291 } 292 293 /// ditto 294 void writestring(scope const(char)[] s) pure nothrow @safe 295 { 296 write(s); 297 } 298 299 /// ditto 300 void writestring(scope string s) pure nothrow @safe 301 { 302 write(s); 303 } 304 305 /// NOT zero-terminated, followed by newline 306 void writestringln(const(char)[] s) pure nothrow @safe 307 { 308 writestring(s); 309 writenl(); 310 } 311 312 /** Write string to buffer, ensure it is zero terminated 313 */ 314 void writeStringz(const(char)* s) pure nothrow @system 315 { 316 write(s[0 .. strlen(s)+1]); 317 } 318 319 /// ditto 320 void writeStringz(const(char)[] s) pure nothrow @safe 321 { 322 write(s); 323 writeByte(0); 324 } 325 326 /// ditto 327 void writeStringz(string s) pure nothrow @safe 328 { 329 writeStringz(cast(const(char)[])(s)); 330 } 331 332 extern (C++) void prependstring(const(char)* string) pure nothrow @system 333 { 334 size_t len = strlen(string); 335 reserve(len); 336 memmove(data.ptr + len, data.ptr, offset); 337 memcpy(data.ptr, string, len); 338 offset += len; 339 } 340 341 /// strip trailing tabs or spaces, write newline 342 extern (C++) void writenl() pure nothrow @safe 343 { 344 while (offset > 0 && (data[offset - 1] == ' ' || data[offset - 1] == '\t')) 345 offset--; 346 347 version (Windows) 348 { 349 writeword(0x0A0D); // newline is CR,LF on Microsoft OS's 350 } 351 else 352 { 353 writeByte('\n'); 354 } 355 if (doindent) 356 notlinehead = false; 357 } 358 359 // Write n zeros; return pointer to start of zeros 360 @trusted 361 void *writezeros(size_t n) nothrow 362 { 363 reserve(n); 364 auto result = memset(data.ptr + offset, 0, n); 365 offset += n; 366 return result; 367 } 368 369 // Position buffer to accept the specified number of bytes at offset 370 @trusted 371 void position(size_t where, size_t nbytes) nothrow 372 { 373 if (where + nbytes > data.length) 374 { 375 reserve(where + nbytes - offset); 376 } 377 offset = where; 378 379 debug assert(offset + nbytes <= data.length); 380 } 381 382 /** 383 * Writes an 8 bit byte, no reserve check. 384 */ 385 extern (C++) @trusted nothrow 386 void writeByten(int b) 387 { 388 this.data[offset++] = cast(ubyte) b; 389 } 390 391 extern (C++) void writeByte(uint b) pure nothrow @safe 392 { 393 if (doindent && !notlinehead && b != '\n') 394 indent(); 395 reserve(1); 396 this.data[offset] = cast(ubyte)b; 397 offset++; 398 } 399 400 extern (C++) void writeUTF8(uint b) pure nothrow @safe 401 { 402 reserve(6); 403 if (b <= 0x7F) 404 { 405 this.data[offset] = cast(ubyte)b; 406 offset++; 407 } 408 else if (b <= 0x7FF) 409 { 410 this.data[offset + 0] = cast(ubyte)((b >> 6) | 0xC0); 411 this.data[offset + 1] = cast(ubyte)((b & 0x3F) | 0x80); 412 offset += 2; 413 } 414 else if (b <= 0xFFFF) 415 { 416 this.data[offset + 0] = cast(ubyte)((b >> 12) | 0xE0); 417 this.data[offset + 1] = cast(ubyte)(((b >> 6) & 0x3F) | 0x80); 418 this.data[offset + 2] = cast(ubyte)((b & 0x3F) | 0x80); 419 offset += 3; 420 } 421 else if (b <= 0x1FFFFF) 422 { 423 this.data[offset + 0] = cast(ubyte)((b >> 18) | 0xF0); 424 this.data[offset + 1] = cast(ubyte)(((b >> 12) & 0x3F) | 0x80); 425 this.data[offset + 2] = cast(ubyte)(((b >> 6) & 0x3F) | 0x80); 426 this.data[offset + 3] = cast(ubyte)((b & 0x3F) | 0x80); 427 offset += 4; 428 } 429 else 430 assert(0); 431 } 432 433 extern (C++) void prependbyte(uint b) pure nothrow @trusted 434 { 435 reserve(1); 436 memmove(data.ptr + 1, data.ptr, offset); 437 data[0] = cast(ubyte)b; 438 offset++; 439 } 440 441 extern (C++) void writewchar(uint w) pure nothrow @safe 442 { 443 version (Windows) 444 { 445 writeword(w); 446 } 447 else 448 { 449 write4(w); 450 } 451 } 452 453 extern (C++) void writeword(uint w) pure nothrow @trusted 454 { 455 version (Windows) 456 { 457 uint newline = 0x0A0D; 458 } 459 else 460 { 461 uint newline = '\n'; 462 } 463 if (doindent && !notlinehead && w != newline) 464 indent(); 465 466 reserve(2); 467 *cast(ushort*)(this.data.ptr + offset) = cast(ushort)w; 468 offset += 2; 469 } 470 471 extern (C++) void writeUTF16(uint w) pure nothrow @trusted 472 { 473 reserve(4); 474 if (w <= 0xFFFF) 475 { 476 *cast(ushort*)(this.data.ptr + offset) = cast(ushort)w; 477 offset += 2; 478 } 479 else if (w <= 0x10FFFF) 480 { 481 *cast(ushort*)(this.data.ptr + offset) = cast(ushort)((w >> 10) + 0xD7C0); 482 *cast(ushort*)(this.data.ptr + offset + 2) = cast(ushort)((w & 0x3FF) | 0xDC00); 483 offset += 4; 484 } 485 else 486 assert(0); 487 } 488 489 extern (C++) void write4(uint w) pure nothrow @trusted 490 { 491 version (Windows) 492 { 493 bool notnewline = w != 0x000A000D; 494 } 495 else 496 { 497 bool notnewline = true; 498 } 499 if (doindent && !notlinehead && notnewline) 500 indent(); 501 reserve(4); 502 *cast(uint*)(this.data.ptr + offset) = w; 503 offset += 4; 504 } 505 506 extern (C++) void write(const OutBuffer* buf) pure nothrow @trusted 507 { 508 if (buf) 509 { 510 reserve(buf.offset); 511 memcpy(data.ptr + offset, buf.data.ptr, buf.offset); 512 offset += buf.offset; 513 } 514 } 515 516 extern (C++) void fill0(size_t nbytes) pure nothrow @trusted 517 { 518 reserve(nbytes); 519 memset(data.ptr + offset, 0, nbytes); 520 offset += nbytes; 521 } 522 523 /** 524 * Allocate space, but leave it uninitialized. 525 * Params: 526 * nbytes = amount to allocate 527 * Returns: 528 * slice of the allocated space to be filled in 529 */ 530 extern (D) char[] allocate(size_t nbytes) pure nothrow 531 { 532 reserve(nbytes); 533 offset += nbytes; 534 return cast(char[])data[offset - nbytes .. offset]; 535 } 536 537 extern (C++) void vprintf(const(char)* format, va_list args) nothrow @system 538 { 539 int count; 540 if (doindent && !notlinehead) 541 indent(); 542 uint psize = 128; 543 for (;;) 544 { 545 reserve(psize); 546 va_list va; 547 va_copy(va, args); 548 /* 549 The functions vprintf(), vfprintf(), vsprintf(), vsnprintf() 550 are equivalent to the functions printf(), fprintf(), sprintf(), 551 snprintf(), respectively, except that they are called with a 552 va_list instead of a variable number of arguments. These 553 functions do not call the va_end macro. Consequently, the value 554 of ap is undefined after the call. The application should call 555 va_end(ap) itself afterwards. 556 */ 557 count = vsnprintf(cast(char*)data.ptr + offset, psize, format, va); 558 va_end(va); 559 if (count == -1) // snn.lib and older libcmt.lib return -1 if buffer too small 560 psize *= 2; 561 else if (count >= psize) 562 psize = count + 1; 563 else 564 break; 565 } 566 offset += count; 567 // if (mem.isGCEnabled) 568 memset(data.ptr + offset, 0xff, psize - count); 569 } 570 571 static if (__VERSION__ < 2092) 572 { 573 extern (C++) void printf(const(char)* format, ...) nothrow @system 574 { 575 va_list ap; 576 va_start(ap, format); 577 vprintf(format, ap); 578 va_end(ap); 579 } 580 } 581 else 582 { 583 pragma(printf) extern (C++) void printf(const(char)* format, ...) nothrow @system 584 { 585 va_list ap; 586 va_start(ap, format); 587 vprintf(format, ap); 588 va_end(ap); 589 } 590 } 591 592 /************************************** 593 * Convert `u` to a string and append it to the buffer. 594 * Params: 595 * u = integral value to append 596 */ 597 extern (C++) void print(ulong u) pure nothrow @safe 598 { 599 UnsignedStringBuf buf = void; 600 writestring(unsignedToTempString(u, buf)); 601 } 602 603 extern (C++) void bracket(char left, char right) pure nothrow @trusted 604 { 605 reserve(2); 606 memmove(data.ptr + 1, data.ptr, offset); 607 data[0] = left; 608 data[offset + 1] = right; 609 offset += 2; 610 } 611 612 /****************** 613 * Insert left at i, and right at j. 614 * Return index just past right. 615 */ 616 extern (C++) size_t bracket(size_t i, const(char)* left, size_t j, const(char)* right) pure nothrow @system 617 { 618 size_t leftlen = strlen(left); 619 size_t rightlen = strlen(right); 620 reserve(leftlen + rightlen); 621 insert(i, left, leftlen); 622 insert(j + leftlen, right, rightlen); 623 return j + leftlen + rightlen; 624 } 625 626 extern (C++) void spread(size_t offset, size_t nbytes) pure nothrow @system 627 { 628 reserve(nbytes); 629 memmove(data.ptr + offset + nbytes, data.ptr + offset, this.offset - offset); 630 this.offset += nbytes; 631 } 632 633 /**************************************** 634 * Returns: offset + nbytes 635 */ 636 extern (C++) size_t insert(size_t offset, const(void)* p, size_t nbytes) pure nothrow @system 637 { 638 spread(offset, nbytes); 639 memmove(data.ptr + offset, p, nbytes); 640 return offset + nbytes; 641 } 642 643 size_t insert(size_t offset, const(char)[] s) pure nothrow @system 644 { 645 return insert(offset, s.ptr, s.length); 646 } 647 648 extern (C++) void remove(size_t offset, size_t nbytes) pure nothrow @nogc @system 649 { 650 memmove(data.ptr + offset, data.ptr + offset + nbytes, this.offset - (offset + nbytes)); 651 this.offset -= nbytes; 652 } 653 654 /** 655 * Returns: 656 * a non-owning const slice of the buffer contents 657 */ 658 extern (D) const(char)[] opSlice() const pure nothrow @nogc @safe 659 { 660 return cast(const(char)[])data[0 .. offset]; 661 } 662 663 extern (D) const(char)[] opSlice(size_t lwr, size_t upr) const pure nothrow @nogc @safe 664 { 665 return cast(const(char)[])data[lwr .. upr]; 666 } 667 668 extern (D) char opIndex(size_t i) const pure nothrow @nogc @safe 669 { 670 return cast(char)data[i]; 671 } 672 673 alias opDollar = length; 674 675 /*********************************** 676 * Extract the data as a slice and take ownership of it. 677 * 678 * When `true` is passed as an argument, this function behaves 679 * like `dmd.utils.toDString(thisbuffer.extractChars())`. 680 * 681 * Params: 682 * nullTerminate = When `true`, the data will be `null` terminated. 683 * This is useful to call C functions or store 684 * the result in `Strings`. Defaults to `false`. 685 */ 686 extern (D) char[] extractSlice(bool nullTerminate = false) pure nothrow 687 { 688 const length = offset; 689 if (!nullTerminate) 690 return extractData()[0 .. length]; 691 // There's already a terminating `'\0'` 692 if (length && data[length - 1] == '\0') 693 return extractData()[0 .. length - 1]; 694 writeByte(0); 695 return extractData()[0 .. length]; 696 } 697 698 extern (D) byte[] extractUbyteSlice(bool nullTerminate = false) pure nothrow 699 { 700 return cast(byte[]) extractSlice(nullTerminate); 701 } 702 703 // Append terminating null if necessary and get view of internal buffer 704 extern (C++) char* peekChars() pure nothrow 705 { 706 if (!offset || data[offset - 1] != '\0') 707 { 708 writeByte(0); 709 offset--; // allow appending more 710 } 711 return cast(char*)data.ptr; 712 } 713 714 // Append terminating null if necessary and take ownership of data 715 extern (C++) char* extractChars() pure nothrow 716 { 717 if (!offset || data[offset - 1] != '\0') 718 writeByte(0); 719 return extractData(); 720 } 721 722 void writesLEB128(int value) pure nothrow @safe 723 { 724 while (1) 725 { 726 ubyte b = value & 0x7F; 727 728 value >>= 7; // arithmetic right shift 729 if ((value == 0 && !(b & 0x40)) || 730 (value == -1 && (b & 0x40))) 731 { 732 writeByte(b); 733 break; 734 } 735 writeByte(b | 0x80); 736 } 737 } 738 739 void writeuLEB128(uint value) pure nothrow @safe 740 { 741 do 742 { 743 ubyte b = value & 0x7F; 744 745 value >>= 7; 746 if (value) 747 b |= 0x80; 748 writeByte(b); 749 } while (value); 750 } 751 752 /** 753 Destructively saves the contents of `this` to `filename`. As an 754 optimization, if the file already has identical contents with the buffer, 755 no copying is done. This is because on SSD drives reading is often much 756 faster than writing and because there's a high likelihood an identical 757 file is written during the build process. 758 759 Params: 760 filename = the name of the file to receive the contents 761 762 Returns: `true` iff the operation succeeded. 763 */ 764 extern(D) bool moveToFile(const char* filename) @system 765 { 766 bool result = true; 767 const bool identical = this[] == FileMapping!(const ubyte)(filename)[]; 768 769 if (fileMapping && fileMapping.active) 770 { 771 // Defer to corresponding functions in FileMapping. 772 if (identical) 773 { 774 result = fileMapping.discard(); 775 } 776 else 777 { 778 // Resize to fit to get rid of the slack bytes at the end 779 fileMapping.resize(offset); 780 result = fileMapping.moveToFile(filename); 781 } 782 // Can't call destroy() here because the file mapping is already closed. 783 data = null; 784 offset = 0; 785 } 786 else 787 { 788 if (!identical) 789 writeFile(filename, this[]); 790 destroy(); 791 } 792 793 return identical 794 ? result && touchFile(filename) 795 : result; 796 } 797 } 798 799 /****** copied from core.internal.string *************/ 800 801 private: 802 803 alias UnsignedStringBuf = char[20]; 804 805 char[] unsignedToTempString(ulong value, return scope char[] buf, uint radix = 10) @safe pure nothrow @nogc 806 { 807 size_t i = buf.length; 808 do 809 { 810 if (value < radix) 811 { 812 ubyte x = cast(ubyte)value; 813 buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a'); 814 break; 815 } 816 else 817 { 818 ubyte x = cast(ubyte)(value % radix); 819 value /= radix; 820 buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a'); 821 } 822 } while (value); 823 return buf[i .. $]; 824 } 825 826 /************* unit tests **************************************************/ 827 828 unittest 829 { 830 OutBuffer buf; 831 buf.printf("betty"); 832 buf.insert(1, "xx".ptr, 2); 833 buf.insert(3, "yy"); 834 buf.remove(4, 1); 835 buf.bracket('(', ')'); 836 const char[] s = buf[]; 837 assert(s == "(bxxyetty)"); 838 buf.destroy(); 839 } 840 841 unittest 842 { 843 OutBuffer buf; 844 buf.writestring("abc".ptr); 845 buf.prependstring("def"); 846 buf.prependbyte('x'); 847 OutBuffer buf2; 848 buf2.writestring("mmm"); 849 buf.write(&buf2); 850 char[] s = buf.extractSlice(); 851 assert(s == "xdefabcmmm"); 852 } 853 854 unittest 855 { 856 OutBuffer buf; 857 buf.writeByte('a'); 858 char[] s = buf.extractSlice(); 859 assert(s == "a"); 860 861 buf.writeByte('b'); 862 char[] t = buf.extractSlice(); 863 assert(t == "b"); 864 } 865 866 unittest 867 { 868 OutBuffer buf; 869 char* p = buf.peekChars(); 870 assert(*p == 0); 871 872 buf.writeByte('s'); 873 char* q = buf.peekChars(); 874 assert(strcmp(q, "s") == 0); 875 } 876 877 unittest 878 { 879 char[10] buf; 880 char[] s = unsignedToTempString(278, buf[], 10); 881 assert(s == "278"); 882 883 s = unsignedToTempString(1, buf[], 10); 884 assert(s == "1"); 885 886 s = unsignedToTempString(8, buf[], 2); 887 assert(s == "1000"); 888 889 s = unsignedToTempString(29, buf[], 16); 890 assert(s == "1d"); 891 } 892 893 unittest 894 { 895 OutBuffer buf; 896 buf.writeUTF8(0x0000_0011); 897 buf.writeUTF8(0x0000_0111); 898 buf.writeUTF8(0x0000_1111); 899 buf.writeUTF8(0x0001_1111); 900 buf.writeUTF8(0x0010_0000); 901 assert(buf[] == "\x11\U00000111\U00001111\U00011111\U00100000"); 902 903 buf.reset(); 904 buf.writeUTF16(0x0000_0011); 905 buf.writeUTF16(0x0010_FFFF); 906 assert(buf[] == cast(string) "\u0011\U0010FFFF"w); 907 } 908 909 unittest 910 { 911 OutBuffer buf; 912 buf.doindent = true; 913 914 const(char)[] s = "abc"; 915 buf.writestring(s); 916 buf.level += 1; 917 buf.indent(); 918 buf.writestring("abs"); 919 920 assert(buf[] == "abc\tabs"); 921 922 buf.setsize(4); 923 assert(buf.length == 4); 924 } 925 926 unittest 927 { 928 OutBuffer buf; 929 930 buf.writenl(); 931 buf.writestring("abc \t "); 932 buf.writenl(); // strips trailing whitespace 933 buf.writenl(); // doesn't strip previous newline 934 935 version(Windows) 936 assert(buf[] == "\r\nabc\r\n\r\n"); 937 else 938 assert(buf[] == "\nabc\n\n"); 939 }