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