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 }