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 @safe 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 @safe 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 // Peek at slice of data without taking ownership 715 extern (D) ubyte[] peekSlice() pure nothrow 716 { 717 return data[0 .. offset]; 718 } 719 720 // Append terminating null if necessary and take ownership of data 721 extern (C++) char* extractChars() pure nothrow @safe 722 { 723 if (!offset || data[offset - 1] != '\0') 724 writeByte(0); 725 return extractData(); 726 } 727 728 void writesLEB128(int value) pure nothrow @safe 729 { 730 while (1) 731 { 732 ubyte b = value & 0x7F; 733 734 value >>= 7; // arithmetic right shift 735 if ((value == 0 && !(b & 0x40)) || 736 (value == -1 && (b & 0x40))) 737 { 738 writeByte(b); 739 break; 740 } 741 writeByte(b | 0x80); 742 } 743 } 744 745 void writeuLEB128(uint value) pure nothrow @safe 746 { 747 do 748 { 749 ubyte b = value & 0x7F; 750 751 value >>= 7; 752 if (value) 753 b |= 0x80; 754 writeByte(b); 755 } while (value); 756 } 757 758 /** 759 Destructively saves the contents of `this` to `filename`. As an 760 optimization, if the file already has identical contents with the buffer, 761 no copying is done. This is because on SSD drives reading is often much 762 faster than writing and because there's a high likelihood an identical 763 file is written during the build process. 764 765 Params: 766 filename = the name of the file to receive the contents 767 768 Returns: `true` iff the operation succeeded. 769 */ 770 extern(D) bool moveToFile(const char* filename) @system 771 { 772 bool result = true; 773 const bool identical = this[] == FileMapping!(const ubyte)(filename)[]; 774 775 if (fileMapping && fileMapping.active) 776 { 777 // Defer to corresponding functions in FileMapping. 778 if (identical) 779 { 780 result = fileMapping.discard(); 781 } 782 else 783 { 784 // Resize to fit to get rid of the slack bytes at the end 785 fileMapping.resize(offset); 786 result = fileMapping.moveToFile(filename); 787 } 788 // Can't call destroy() here because the file mapping is already closed. 789 data = null; 790 offset = 0; 791 } 792 else 793 { 794 if (!identical) 795 writeFile(filename, this[]); 796 destroy(); 797 } 798 799 return identical 800 ? result && touchFile(filename) 801 : result; 802 } 803 } 804 805 /****** copied from core.internal.string *************/ 806 807 private: 808 809 alias UnsignedStringBuf = char[20]; 810 811 char[] unsignedToTempString(ulong value, return scope char[] buf, uint radix = 10) @safe pure nothrow @nogc 812 { 813 size_t i = buf.length; 814 do 815 { 816 if (value < radix) 817 { 818 ubyte x = cast(ubyte)value; 819 buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a'); 820 break; 821 } 822 else 823 { 824 ubyte x = cast(ubyte)(value % radix); 825 value /= radix; 826 buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a'); 827 } 828 } while (value); 829 return buf[i .. $]; 830 } 831 832 /************* unit tests **************************************************/ 833 834 unittest 835 { 836 OutBuffer buf; 837 buf.printf("betty"); 838 buf.insert(1, "xx".ptr, 2); 839 buf.insert(3, "yy"); 840 buf.remove(4, 1); 841 buf.bracket('(', ')'); 842 const char[] s = buf[]; 843 assert(s == "(bxxyetty)"); 844 buf.destroy(); 845 } 846 847 unittest 848 { 849 OutBuffer buf; 850 buf.writestring("abc".ptr); 851 buf.prependstring("def"); 852 buf.prependbyte('x'); 853 OutBuffer buf2; 854 buf2.writestring("mmm"); 855 buf.write(&buf2); 856 char[] s = buf.extractSlice(); 857 assert(s == "xdefabcmmm"); 858 } 859 860 unittest 861 { 862 OutBuffer buf; 863 buf.writeByte('a'); 864 char[] s = buf.extractSlice(); 865 assert(s == "a"); 866 867 buf.writeByte('b'); 868 char[] t = buf.extractSlice(); 869 assert(t == "b"); 870 } 871 872 unittest 873 { 874 OutBuffer buf; 875 char* p = buf.peekChars(); 876 assert(*p == 0); 877 878 buf.writeByte('s'); 879 char* q = buf.peekChars(); 880 assert(strcmp(q, "s") == 0); 881 } 882 883 unittest 884 { 885 char[10] buf; 886 char[] s = unsignedToTempString(278, buf[], 10); 887 assert(s == "278"); 888 889 s = unsignedToTempString(1, buf[], 10); 890 assert(s == "1"); 891 892 s = unsignedToTempString(8, buf[], 2); 893 assert(s == "1000"); 894 895 s = unsignedToTempString(29, buf[], 16); 896 assert(s == "1d"); 897 } 898 899 unittest 900 { 901 OutBuffer buf; 902 buf.writeUTF8(0x0000_0011); 903 buf.writeUTF8(0x0000_0111); 904 buf.writeUTF8(0x0000_1111); 905 buf.writeUTF8(0x0001_1111); 906 buf.writeUTF8(0x0010_0000); 907 assert(buf[] == "\x11\U00000111\U00001111\U00011111\U00100000"); 908 909 buf.reset(); 910 buf.writeUTF16(0x0000_0011); 911 buf.writeUTF16(0x0010_FFFF); 912 assert(buf[] == cast(string) "\u0011\U0010FFFF"w); 913 } 914 915 unittest 916 { 917 OutBuffer buf; 918 buf.doindent = true; 919 920 const(char)[] s = "abc"; 921 buf.writestring(s); 922 buf.level += 1; 923 buf.indent(); 924 buf.writestring("abs"); 925 926 assert(buf[] == "abc\tabs"); 927 928 buf.setsize(4); 929 assert(buf.length == 4); 930 } 931 932 unittest 933 { 934 OutBuffer buf; 935 936 buf.writenl(); 937 buf.writestring("abc \t "); 938 buf.writenl(); // strips trailing whitespace 939 buf.writenl(); // doesn't strip previous newline 940 941 version(Windows) 942 assert(buf[] == "\r\nabc\r\n\r\n"); 943 else 944 assert(buf[] == "\nabc\n\n"); 945 }