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 }