1 /** 2 * An expandable buffer in which you can write text or binary data. 3 * 4 * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved 5 * Authors: Walter Bright, http://www.digitalmars.com 6 * License: $(LINK2 http://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.root.outbuffer; 13 14 import core.stdc.stdio; 15 import core.stdc.string; 16 import dmd.root.stdarg; 17 import dmd.root.rmem; 18 import dmd.root.rootobject; 19 import dmd.root.string; 20 21 debug 22 { 23 debug = stomp; // flush out dangling pointer problems by stomping on unused memory 24 } 25 26 private extern (C) pure nothrow @nogc @system 27 { 28 version (CRuntime_DigitalMars) 29 { 30 int _vsnprintf(scope char* s, size_t n, scope const char* format, 31 va_list arg); 32 alias _vsnprintf vsnprintf; 33 } 34 35 else version (CRuntime_Microsoft) 36 { 37 version (MinGW) 38 { 39 int __mingw_vsnprintf(scope char* s, size_t n, 40 scope const char* format, va_list arg); 41 alias __mingw_vsnprintf vsnprintf; 42 } 43 44 else 45 int vsnprintf(scope char* s, size_t n, scope const char* format, 46 va_list arg); 47 } 48 49 else version (Posix) 50 int vsnprintf(scope char* s, size_t n, scope const char* format, 51 va_list arg); 52 else 53 static assert( false, "Unsupported platform" ); 54 } 55 56 struct OutBuffer 57 { 58 private ubyte[] data; 59 private size_t offset; 60 private bool notlinehead; 61 62 /// Whether to indent 63 bool doindent; 64 /// Whether to indent by 4 spaces or by tabs; 65 bool spaces; 66 /// Current indent level 67 int level; 68 69 extern (C++) ~this() pure nothrow 70 { 71 debug (stomp) memset(data.ptr, 0xFF, data.length); 72 mem.xfree(data.ptr); 73 } 74 75 extern (C++) size_t length() const pure @nogc @safe nothrow { return offset; } 76 77 /********************** 78 * Transfer ownership of the allocated data to the caller. 79 * Returns: 80 * pointer to the allocated data 81 */ 82 extern (C++) char* extractData() pure nothrow @nogc @trusted 83 { 84 char* p = cast(char*)data.ptr; 85 data = null; 86 offset = 0; 87 return p; 88 } 89 90 extern (C++) void destroy() pure nothrow @trusted 91 { 92 debug (stomp) memset(data.ptr, 0xFF, data.length); 93 mem.xfree(extractData()); 94 } 95 96 extern (C++) void reserve(size_t nbytes) pure nothrow 97 { 98 //debug (stomp) printf("OutBuffer::reserve: size = %lld, offset = %lld, nbytes = %lld\n", data.length, offset, nbytes); 99 if (data.length - offset < nbytes) 100 { 101 /* Increase by factor of 1.5; round up to 16 bytes. 102 * The odd formulation is so it will map onto single x86 LEA instruction. 103 */ 104 const size = (((offset + nbytes) * 3 + 30) / 2) & ~15; 105 106 debug (stomp) 107 { 108 auto p = cast(ubyte*)mem.xmalloc(size); 109 memcpy(p, data.ptr, offset); 110 memset(data.ptr, 0xFF, data.length); // stomp old location 111 mem.xfree(data.ptr); 112 memset(p + offset, 0xff, size - offset); // stomp unused data 113 } 114 else 115 { 116 auto p = cast(ubyte*)mem.xrealloc(data.ptr, size); 117 if (mem.isGCEnabled) // clear currently unused data to avoid false pointers 118 memset(p + offset + nbytes, 0xff, size - offset - nbytes); 119 } 120 data = p[0 .. size]; 121 } 122 } 123 124 /************************ 125 * Shrink the size of the data to `size`. 126 * Params: 127 * size = new size of data, must be <= `.length` 128 */ 129 extern (C++) void setsize(size_t size) pure nothrow @nogc @safe 130 { 131 assert(size <= offset); 132 offset = size; 133 } 134 135 extern (C++) void reset() pure nothrow @nogc @safe 136 { 137 offset = 0; 138 } 139 140 private void indent() pure nothrow 141 { 142 if (level) 143 { 144 const indentLevel = spaces ? level * 4 : level; 145 reserve(indentLevel); 146 data[offset .. offset + indentLevel] = (spaces ? ' ' : '\t'); 147 offset += indentLevel; 148 } 149 notlinehead = true; 150 } 151 152 extern (C++) void write(const(void)* data, size_t nbytes) pure nothrow 153 { 154 write(data[0 .. nbytes]); 155 } 156 157 void write(const(void)[] buf) pure nothrow 158 { 159 if (doindent && !notlinehead) 160 indent(); 161 reserve(buf.length); 162 memcpy(this.data.ptr + offset, buf.ptr, buf.length); 163 offset += buf.length; 164 } 165 166 extern (C++) void writestring(const(char)* string) pure nothrow 167 { 168 write(string.toDString); 169 } 170 171 void writestring(const(char)[] s) pure nothrow 172 { 173 write(s); 174 } 175 176 void writestring(string s) pure nothrow 177 { 178 write(s); 179 } 180 181 void writestringln(const(char)[] s) 182 { 183 writestring(s); 184 writenl(); 185 } 186 187 extern (C++) void prependstring(const(char)* string) pure nothrow 188 { 189 size_t len = strlen(string); 190 reserve(len); 191 memmove(data.ptr + len, data.ptr, offset); 192 memcpy(data.ptr, string, len); 193 offset += len; 194 } 195 196 /// write newline 197 extern (C++) void writenl() pure nothrow 198 { 199 version (Windows) 200 { 201 writeword(0x0A0D); // newline is CR,LF on Microsoft OS's 202 } 203 else 204 { 205 writeByte('\n'); 206 } 207 if (doindent) 208 notlinehead = false; 209 } 210 211 extern (C++) void writeByte(uint b) pure nothrow 212 { 213 if (doindent && !notlinehead && b != '\n') 214 indent(); 215 reserve(1); 216 this.data[offset] = cast(ubyte)b; 217 offset++; 218 } 219 220 extern (C++) void writeUTF8(uint b) pure nothrow 221 { 222 reserve(6); 223 if (b <= 0x7F) 224 { 225 this.data[offset] = cast(ubyte)b; 226 offset++; 227 } 228 else if (b <= 0x7FF) 229 { 230 this.data[offset + 0] = cast(ubyte)((b >> 6) | 0xC0); 231 this.data[offset + 1] = cast(ubyte)((b & 0x3F) | 0x80); 232 offset += 2; 233 } 234 else if (b <= 0xFFFF) 235 { 236 this.data[offset + 0] = cast(ubyte)((b >> 12) | 0xE0); 237 this.data[offset + 1] = cast(ubyte)(((b >> 6) & 0x3F) | 0x80); 238 this.data[offset + 2] = cast(ubyte)((b & 0x3F) | 0x80); 239 offset += 3; 240 } 241 else if (b <= 0x1FFFFF) 242 { 243 this.data[offset + 0] = cast(ubyte)((b >> 18) | 0xF0); 244 this.data[offset + 1] = cast(ubyte)(((b >> 12) & 0x3F) | 0x80); 245 this.data[offset + 2] = cast(ubyte)(((b >> 6) & 0x3F) | 0x80); 246 this.data[offset + 3] = cast(ubyte)((b & 0x3F) | 0x80); 247 offset += 4; 248 } 249 else 250 assert(0); 251 } 252 253 extern (C++) void prependbyte(uint b) pure nothrow 254 { 255 reserve(1); 256 memmove(data.ptr + 1, data.ptr, offset); 257 data[0] = cast(ubyte)b; 258 offset++; 259 } 260 261 extern (C++) void writewchar(uint w) pure nothrow 262 { 263 version (Windows) 264 { 265 writeword(w); 266 } 267 else 268 { 269 write4(w); 270 } 271 } 272 273 extern (C++) void writeword(uint w) pure nothrow 274 { 275 version (Windows) 276 { 277 uint newline = 0x0A0D; 278 } 279 else 280 { 281 uint newline = '\n'; 282 } 283 if (doindent && !notlinehead && w != newline) 284 indent(); 285 286 reserve(2); 287 *cast(ushort*)(this.data.ptr + offset) = cast(ushort)w; 288 offset += 2; 289 } 290 291 extern (C++) void writeUTF16(uint w) pure nothrow 292 { 293 reserve(4); 294 if (w <= 0xFFFF) 295 { 296 *cast(ushort*)(this.data.ptr + offset) = cast(ushort)w; 297 offset += 2; 298 } 299 else if (w <= 0x10FFFF) 300 { 301 *cast(ushort*)(this.data.ptr + offset) = cast(ushort)((w >> 10) + 0xD7C0); 302 *cast(ushort*)(this.data.ptr + offset + 2) = cast(ushort)((w & 0x3FF) | 0xDC00); 303 offset += 4; 304 } 305 else 306 assert(0); 307 } 308 309 extern (C++) void write4(uint w) pure nothrow 310 { 311 version (Windows) 312 { 313 bool notnewline = w != 0x000A000D; 314 } 315 else 316 { 317 bool notnewline = true; 318 } 319 if (doindent && !notlinehead && notnewline) 320 indent(); 321 reserve(4); 322 *cast(uint*)(this.data.ptr + offset) = w; 323 offset += 4; 324 } 325 326 extern (C++) void write(const OutBuffer* buf) pure nothrow 327 { 328 if (buf) 329 { 330 reserve(buf.offset); 331 memcpy(data.ptr + offset, buf.data.ptr, buf.offset); 332 offset += buf.offset; 333 } 334 } 335 336 extern (C++) void write(RootObject obj) /*nothrow*/ 337 { 338 if (obj) 339 { 340 writestring(obj.toChars()); 341 } 342 } 343 344 extern (C++) void fill0(size_t nbytes) pure nothrow 345 { 346 reserve(nbytes); 347 memset(data.ptr + offset, 0, nbytes); 348 offset += nbytes; 349 } 350 351 /** 352 * Allocate space, but leave it uninitialized. 353 * Params: 354 * nbytes = amount to allocate 355 * Returns: 356 * slice of the allocated space to be filled in 357 */ 358 extern (D) char[] allocate(size_t nbytes) pure nothrow 359 { 360 reserve(nbytes); 361 offset += nbytes; 362 return cast(char[])data[offset - nbytes .. offset]; 363 } 364 365 extern (C++) void vprintf(scope const(char)* format, va_list args) pure nothrow 366 { 367 int count; 368 if (doindent && !notlinehead) 369 indent(); 370 uint psize = 128; 371 for (;;) 372 { 373 reserve(psize); 374 va_list va; 375 va_copy(va, args); 376 /* 377 The functions vprintf(), vfprintf(), vsprintf(), vsnprintf() 378 are equivalent to the functions printf(), fprintf(), sprintf(), 379 snprintf(), respectively, except that they are called with a 380 va_list instead of a variable number of arguments. These 381 functions do not call the va_end macro. Consequently, the value 382 of ap is undefined after the call. The application should call 383 va_end(ap) itself afterwards. 384 */ 385 count = vsnprintf(cast(char*)data.ptr + offset, psize, format, va); 386 va_end(va); 387 if (count == -1) // snn.lib and older libcmt.lib return -1 if buffer too small 388 psize *= 2; 389 else if (count >= psize) 390 psize = count + 1; 391 else 392 break; 393 } 394 offset += count; 395 if (mem.isGCEnabled) 396 memset(data.ptr + offset, 0xff, psize - count); 397 } 398 399 static if (__VERSION__ < 2092) 400 { 401 extern (C++) void printf(const(char)* format, ...) nothrow 402 { 403 va_list ap; 404 va_start(ap, format); 405 vprintf(format, ap); 406 va_end(ap); 407 } 408 } 409 else 410 { 411 pragma(printf) extern (C++) void printf(const(char)* format, ...) nothrow 412 { 413 va_list ap; 414 va_start(ap, format); 415 vprintf(format, ap); 416 va_end(ap); 417 } 418 } 419 420 /************************************** 421 * Convert `u` to a string and append it to the buffer. 422 * Params: 423 * u = integral value to append 424 */ 425 extern (C++) void print(ulong u) pure nothrow 426 { 427 //import core.internal.string; // not available 428 UnsignedStringBuf buf = void; 429 writestring(unsignedToTempString(u, buf)); 430 } 431 432 extern (C++) void bracket(char left, char right) pure nothrow 433 { 434 reserve(2); 435 memmove(data.ptr + 1, data.ptr, offset); 436 data[0] = left; 437 data[offset + 1] = right; 438 offset += 2; 439 } 440 441 /****************** 442 * Insert left at i, and right at j. 443 * Return index just past right. 444 */ 445 extern (C++) size_t bracket(size_t i, const(char)* left, size_t j, const(char)* right) pure nothrow 446 { 447 size_t leftlen = strlen(left); 448 size_t rightlen = strlen(right); 449 reserve(leftlen + rightlen); 450 insert(i, left, leftlen); 451 insert(j + leftlen, right, rightlen); 452 return j + leftlen + rightlen; 453 } 454 455 extern (C++) void spread(size_t offset, size_t nbytes) pure nothrow 456 { 457 reserve(nbytes); 458 memmove(data.ptr + offset + nbytes, data.ptr + offset, this.offset - offset); 459 this.offset += nbytes; 460 } 461 462 /**************************************** 463 * Returns: offset + nbytes 464 */ 465 extern (C++) size_t insert(size_t offset, const(void)* p, size_t nbytes) pure nothrow 466 { 467 spread(offset, nbytes); 468 memmove(data.ptr + offset, p, nbytes); 469 return offset + nbytes; 470 } 471 472 size_t insert(size_t offset, const(char)[] s) pure nothrow 473 { 474 return insert(offset, s.ptr, s.length); 475 } 476 477 extern (C++) void remove(size_t offset, size_t nbytes) pure nothrow @nogc 478 { 479 memmove(data.ptr + offset, data.ptr + offset + nbytes, this.offset - (offset + nbytes)); 480 this.offset -= nbytes; 481 } 482 483 /** 484 * Returns: 485 * a non-owning const slice of the buffer contents 486 */ 487 extern (D) const(char)[] opSlice() const pure nothrow @nogc 488 { 489 return cast(const(char)[])data[0 .. offset]; 490 } 491 492 extern (D) const(char)[] opSlice(size_t lwr, size_t upr) const pure nothrow @nogc 493 { 494 return cast(const(char)[])data[lwr .. upr]; 495 } 496 497 extern (D) char opIndex(size_t i) const pure nothrow @nogc 498 { 499 return cast(char)data[i]; 500 } 501 502 /*********************************** 503 * Extract the data as a slice and take ownership of it. 504 * 505 * When `true` is passed as an argument, this function behaves 506 * like `dmd.utils.toDString(thisbuffer.extractChars())`. 507 * 508 * Params: 509 * nullTerminate = When `true`, the data will be `null` terminated. 510 * This is useful to call C functions or store 511 * the result in `Strings`. Defaults to `false`. 512 */ 513 extern (D) char[] extractSlice(bool nullTerminate = false) pure nothrow 514 { 515 const length = offset; 516 if (!nullTerminate) 517 return extractData()[0 .. length]; 518 // There's already a terminating `'\0'` 519 if (length && data[length - 1] == '\0') 520 return extractData()[0 .. length - 1]; 521 writeByte(0); 522 return extractData()[0 .. length]; 523 } 524 525 // Append terminating null if necessary and get view of internal buffer 526 extern (C++) char* peekChars() pure nothrow 527 { 528 if (!offset || data[offset - 1] != '\0') 529 { 530 writeByte(0); 531 offset--; // allow appending more 532 } 533 return cast(char*)data.ptr; 534 } 535 536 // Append terminating null if necessary and take ownership of data 537 extern (C++) char* extractChars() pure nothrow 538 { 539 if (!offset || data[offset - 1] != '\0') 540 writeByte(0); 541 return extractData(); 542 } 543 } 544 545 /****** copied from core.internal.string *************/ 546 547 private: 548 549 alias UnsignedStringBuf = char[20]; 550 551 char[] unsignedToTempString(ulong value, char[] buf, uint radix = 10) @safe pure nothrow @nogc 552 { 553 size_t i = buf.length; 554 do 555 { 556 if (value < radix) 557 { 558 ubyte x = cast(ubyte)value; 559 buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a'); 560 break; 561 } 562 else 563 { 564 ubyte x = cast(ubyte)(value % radix); 565 value = value / radix; 566 buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a'); 567 } 568 } while (value); 569 return buf[i .. $]; 570 } 571 572 /************* unit tests **************************************************/ 573 574 unittest 575 { 576 OutBuffer buf; 577 buf.printf("betty"); 578 buf.insert(1, "xx".ptr, 2); 579 buf.insert(3, "yy"); 580 buf.remove(4, 1); 581 buf.bracket('(', ')'); 582 const char[] s = buf[]; 583 assert(s == "(bxxyetty)"); 584 buf.destroy(); 585 } 586 587 unittest 588 { 589 OutBuffer buf; 590 buf.writestring("abc".ptr); 591 buf.prependstring("def"); 592 buf.prependbyte('x'); 593 OutBuffer buf2; 594 buf2.writestring("mmm"); 595 buf.write(&buf2); 596 char[] s = buf.extractSlice(); 597 assert(s == "xdefabcmmm"); 598 } 599 600 unittest 601 { 602 OutBuffer buf; 603 buf.writeByte('a'); 604 char[] s = buf.extractSlice(); 605 assert(s == "a"); 606 607 buf.writeByte('b'); 608 char[] t = buf.extractSlice(); 609 assert(t == "b"); 610 } 611 612 unittest 613 { 614 OutBuffer buf; 615 char* p = buf.peekChars(); 616 assert(*p == 0); 617 618 buf.writeByte('s'); 619 char* q = buf.peekChars(); 620 assert(strcmp(q, "s") == 0); 621 } 622 623 unittest 624 { 625 char[10] buf; 626 char[] s = unsignedToTempString(278, buf[], 10); 627 assert(s == "278"); 628 629 s = unsignedToTempString(1, buf[], 10); 630 assert(s == "1"); 631 632 s = unsignedToTempString(8, buf[], 2); 633 assert(s == "1000"); 634 635 s = unsignedToTempString(29, buf[], 16); 636 assert(s == "1d"); 637 }