1 /** 2 * An expandable buffer in which you can write text or binary data. 3 * 4 * Copyright: Copyright (C) 1999-2020 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 extern (C++) void printf(const(char)* format, ...) nothrow 400 { 401 va_list ap; 402 va_start(ap, format); 403 vprintf(format, ap); 404 va_end(ap); 405 } 406 407 /************************************** 408 * Convert `u` to a string and append it to the buffer. 409 * Params: 410 * u = integral value to append 411 */ 412 extern (C++) void print(ulong u) pure nothrow 413 { 414 //import core.internal.string; // not available 415 UnsignedStringBuf buf = void; 416 writestring(unsignedToTempString(u, buf)); 417 } 418 419 extern (C++) void bracket(char left, char right) pure nothrow 420 { 421 reserve(2); 422 memmove(data.ptr + 1, data.ptr, offset); 423 data[0] = left; 424 data[offset + 1] = right; 425 offset += 2; 426 } 427 428 /****************** 429 * Insert left at i, and right at j. 430 * Return index just past right. 431 */ 432 extern (C++) size_t bracket(size_t i, const(char)* left, size_t j, const(char)* right) pure nothrow 433 { 434 size_t leftlen = strlen(left); 435 size_t rightlen = strlen(right); 436 reserve(leftlen + rightlen); 437 insert(i, left, leftlen); 438 insert(j + leftlen, right, rightlen); 439 return j + leftlen + rightlen; 440 } 441 442 extern (C++) void spread(size_t offset, size_t nbytes) pure nothrow 443 { 444 reserve(nbytes); 445 memmove(data.ptr + offset + nbytes, data.ptr + offset, this.offset - offset); 446 this.offset += nbytes; 447 } 448 449 /**************************************** 450 * Returns: offset + nbytes 451 */ 452 extern (C++) size_t insert(size_t offset, const(void)* p, size_t nbytes) pure nothrow 453 { 454 spread(offset, nbytes); 455 memmove(data.ptr + offset, p, nbytes); 456 return offset + nbytes; 457 } 458 459 size_t insert(size_t offset, const(char)[] s) pure nothrow 460 { 461 return insert(offset, s.ptr, s.length); 462 } 463 464 extern (C++) void remove(size_t offset, size_t nbytes) pure nothrow @nogc 465 { 466 memmove(data.ptr + offset, data.ptr + offset + nbytes, this.offset - (offset + nbytes)); 467 this.offset -= nbytes; 468 } 469 470 /** 471 * Returns: 472 * a non-owning const slice of the buffer contents 473 */ 474 extern (D) const(char)[] opSlice() const pure nothrow @nogc 475 { 476 return cast(const(char)[])data[0 .. offset]; 477 } 478 479 extern (D) const(char)[] opSlice(size_t lwr, size_t upr) const pure nothrow @nogc 480 { 481 return cast(const(char)[])data[lwr .. upr]; 482 } 483 484 extern (D) char opIndex(size_t i) const pure nothrow @nogc 485 { 486 return cast(char)data[i]; 487 } 488 489 /*********************************** 490 * Extract the data as a slice and take ownership of it. 491 * 492 * When `true` is passed as an argument, this function behaves 493 * like `dmd.utils.toDString(thisbuffer.extractChars())`. 494 * 495 * Params: 496 * nullTerminate = When `true`, the data will be `null` terminated. 497 * This is useful to call C functions or store 498 * the result in `Strings`. Defaults to `false`. 499 */ 500 extern (D) char[] extractSlice(bool nullTerminate = false) pure nothrow 501 { 502 const length = offset; 503 if (!nullTerminate) 504 return extractData()[0 .. length]; 505 // There's already a terminating `'\0'` 506 if (length && data[length - 1] == '\0') 507 return extractData()[0 .. length - 1]; 508 writeByte(0); 509 return extractData()[0 .. length]; 510 } 511 512 // Append terminating null if necessary and get view of internal buffer 513 extern (C++) char* peekChars() pure nothrow 514 { 515 if (!offset || data[offset - 1] != '\0') 516 { 517 writeByte(0); 518 offset--; // allow appending more 519 } 520 return cast(char*)data.ptr; 521 } 522 523 // Append terminating null if necessary and take ownership of data 524 extern (C++) char* extractChars() pure nothrow 525 { 526 if (!offset || data[offset - 1] != '\0') 527 writeByte(0); 528 return extractData(); 529 } 530 } 531 532 /****** copied from core.internal.string *************/ 533 534 private: 535 536 alias UnsignedStringBuf = char[20]; 537 538 char[] unsignedToTempString(ulong value, char[] buf, uint radix = 10) @safe pure nothrow @nogc 539 { 540 size_t i = buf.length; 541 do 542 { 543 if (value < radix) 544 { 545 ubyte x = cast(ubyte)value; 546 buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a'); 547 break; 548 } 549 else 550 { 551 ubyte x = cast(ubyte)(value % radix); 552 value = value / radix; 553 buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a'); 554 } 555 } while (value); 556 return buf[i .. $]; 557 } 558 559 /************* unit tests **************************************************/ 560 561 unittest 562 { 563 OutBuffer buf; 564 buf.printf("betty"); 565 buf.insert(1, "xx".ptr, 2); 566 buf.insert(3, "yy"); 567 buf.remove(4, 1); 568 buf.bracket('(', ')'); 569 const char[] s = buf[]; 570 assert(s == "(bxxyetty)"); 571 buf.destroy(); 572 } 573 574 unittest 575 { 576 OutBuffer buf; 577 buf.writestring("abc".ptr); 578 buf.prependstring("def"); 579 buf.prependbyte('x'); 580 OutBuffer buf2; 581 buf2.writestring("mmm"); 582 buf.write(&buf2); 583 char[] s = buf.extractSlice(); 584 assert(s == "xdefabcmmm"); 585 } 586 587 unittest 588 { 589 OutBuffer buf; 590 buf.writeByte('a'); 591 char[] s = buf.extractSlice(); 592 assert(s == "a"); 593 594 buf.writeByte('b'); 595 char[] t = buf.extractSlice(); 596 assert(t == "b"); 597 } 598 599 unittest 600 { 601 OutBuffer buf; 602 char* p = buf.peekChars(); 603 assert(*p == 0); 604 605 buf.writeByte('s'); 606 char* q = buf.peekChars(); 607 assert(strcmp(q, "s") == 0); 608 } 609 610 unittest 611 { 612 char[10] buf; 613 char[] s = unsignedToTempString(278, buf[], 10); 614 assert(s == "278"); 615 616 s = unsignedToTempString(1, buf[], 10); 617 assert(s == "1"); 618 619 s = unsignedToTempString(8, buf[], 2); 620 assert(s == "1000"); 621 622 s = unsignedToTempString(29, buf[], 16); 623 assert(s == "1d"); 624 }