1 /**
2  * An expandable buffer in which you can write text or binary data.
3  *
4  * Copyright: Copyright (C) 1999-2022 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     */
thisOutBuffer64     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     */
thisOutBuffer76     @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     */
dtorOutBuffer89     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     */
~thisOutBuffer106     extern (C++) ~this() pure nothrow @trusted
107     {
108         dtor();
109     }
110 
111     /// For porting with ease from dmd.backend.outbuf.Outbuffer
bufOutBuffer112     ubyte* buf() nothrow {
113         return data.ptr;
114     }
115 
116     /// For porting with ease from dmd.backend.outbuf.Outbuffer
bufptrOutBuffer117     ubyte** bufptr() nothrow {
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 
lengthOutBuffer124     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      */
extractDataOutBuffer131     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     */
destroyOutBuffer144     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     */
reserveOutBuffer159     extern (C++) void reserve(size_t nbytes) pure nothrow
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      */
setsizeOutBuffer202     extern (C++) void setsize(size_t size) pure nothrow @nogc @safe
203     {
204         assert(size <= data.length);
205         offset = size;
206     }
207 
resetOutBuffer208     extern (C++) void reset() pure nothrow @nogc @safe
209     {
210         offset = 0;
211     }
212 
indentOutBuffer213     private void indent() pure nothrow
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     @trusted nothrow
writenOutBuffer227     void writen(const void *b, size_t len)
228     {
229         memcpy(data.ptr + offset, b, len);
230         offset += len;
231     }
232 
writeOutBuffer233     extern (C++) void write(const(void)* data, size_t nbytes) pure nothrow
234     {
235         write(data[0 .. nbytes]);
236     }
237 
writeOutBuffer238     void write(const(void)[] buf) pure nothrow
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
write16nOutBuffer251     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      */
write16OutBuffer262     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      */
write32OutBuffer271     void write32(int v) nothrow @trusted
272     {
273         write(&v, v.sizeof);
274     }
275 
276     /**
277      * Writes a 64 bit int.
278      */
write64OutBuffer279     @trusted void write64(long v) nothrow
280     {
281         write(&v, v.sizeof);
282     }
283 
284     /// NOT zero-terminated
writestringOutBuffer285     extern (C++) void writestring(const(char)* s) pure nothrow
286     {
287         if (!s)
288             return;
289         import core.stdc.string : strlen;
290         write(s[0 .. strlen(s)]);
291     }
292 
293     /// ditto
writestringOutBuffer294     void writestring(const(char)[] s) pure nothrow
295     {
296         write(s);
297     }
298 
299     /// ditto
writestringOutBuffer300     void writestring(string s) pure nothrow
301     {
302         write(s);
303     }
304 
305     /// NOT zero-terminated, followed by newline
writestringlnOutBuffer306     void writestringln(const(char)[] s) pure nothrow
307     {
308         writestring(s);
309         writenl();
310     }
311 
312     // Zero-terminated
writeStringOutBuffer313     void writeString(const(char)* s) pure nothrow @trusted
314     {
315         write(s[0 .. strlen(s)+1]);
316     }
317 
318     /// ditto
writeStringOutBuffer319     void writeString(const(char)[] s) pure nothrow
320     {
321         write(s);
322         writeByte(0);
323     }
324 
325     /// ditto
writeStringOutBuffer326     void writeString(string s) pure nothrow
327     {
328         writeString(cast(const(char)[])(s));
329     }
330 
prependstringOutBuffer331     extern (C++) void prependstring(const(char)* string) pure nothrow
332     {
333         size_t len = strlen(string);
334         reserve(len);
335         memmove(data.ptr + len, data.ptr, offset);
336         memcpy(data.ptr, string, len);
337         offset += len;
338     }
339 
340     /// write newline
writenlOutBuffer341     extern (C++) void writenl() pure nothrow
342     {
343         version (Windows)
344         {
345             writeword(0x0A0D); // newline is CR,LF on Microsoft OS's
346         }
347         else
348         {
349             writeByte('\n');
350         }
351         if (doindent)
352             notlinehead = false;
353     }
354 
355     // Write n zeros; return pointer to start of zeros
356     @trusted
writezerosOutBuffer357     void *writezeros(size_t n) nothrow
358     {
359         reserve(n);
360         auto result = memset(data.ptr + offset, 0, n);
361         offset += n;
362         return result;
363     }
364 
365     // Position buffer to accept the specified number of bytes at offset
366     @trusted
positionOutBuffer367     void position(size_t where, size_t nbytes) nothrow
368     {
369         if (where + nbytes > data.length)
370         {
371             reserve(where + nbytes - offset);
372         }
373         offset = where;
374 
375         debug assert(offset + nbytes <= data.length);
376     }
377 
378     /**
379      * Writes an 8 bit byte, no reserve check.
380      */
381     extern (C++) @trusted nothrow
writeBytenOutBuffer382     void writeByten(int b)
383     {
384         this.data[offset++] = cast(ubyte) b;
385     }
386 
writeByteOutBuffer387     extern (C++) void writeByte(uint b) pure nothrow
388     {
389         if (doindent && !notlinehead && b != '\n')
390             indent();
391         reserve(1);
392         this.data[offset] = cast(ubyte)b;
393         offset++;
394     }
395 
writeUTF8OutBuffer396     extern (C++) void writeUTF8(uint b) pure nothrow
397     {
398         reserve(6);
399         if (b <= 0x7F)
400         {
401             this.data[offset] = cast(ubyte)b;
402             offset++;
403         }
404         else if (b <= 0x7FF)
405         {
406             this.data[offset + 0] = cast(ubyte)((b >> 6) | 0xC0);
407             this.data[offset + 1] = cast(ubyte)((b & 0x3F) | 0x80);
408             offset += 2;
409         }
410         else if (b <= 0xFFFF)
411         {
412             this.data[offset + 0] = cast(ubyte)((b >> 12) | 0xE0);
413             this.data[offset + 1] = cast(ubyte)(((b >> 6) & 0x3F) | 0x80);
414             this.data[offset + 2] = cast(ubyte)((b & 0x3F) | 0x80);
415             offset += 3;
416         }
417         else if (b <= 0x1FFFFF)
418         {
419             this.data[offset + 0] = cast(ubyte)((b >> 18) | 0xF0);
420             this.data[offset + 1] = cast(ubyte)(((b >> 12) & 0x3F) | 0x80);
421             this.data[offset + 2] = cast(ubyte)(((b >> 6) & 0x3F) | 0x80);
422             this.data[offset + 3] = cast(ubyte)((b & 0x3F) | 0x80);
423             offset += 4;
424         }
425         else
426             assert(0);
427     }
428 
prependbyteOutBuffer429     extern (C++) void prependbyte(uint b) pure nothrow
430     {
431         reserve(1);
432         memmove(data.ptr + 1, data.ptr, offset);
433         data[0] = cast(ubyte)b;
434         offset++;
435     }
436 
writewcharOutBuffer437     extern (C++) void writewchar(uint w) pure nothrow
438     {
439         version (Windows)
440         {
441             writeword(w);
442         }
443         else
444         {
445             write4(w);
446         }
447     }
448 
writewordOutBuffer449     extern (C++) void writeword(uint w) pure nothrow
450     {
451         version (Windows)
452         {
453             uint newline = 0x0A0D;
454         }
455         else
456         {
457             uint newline = '\n';
458         }
459         if (doindent && !notlinehead && w != newline)
460             indent();
461 
462         reserve(2);
463         *cast(ushort*)(this.data.ptr + offset) = cast(ushort)w;
464         offset += 2;
465     }
466 
writeUTF16OutBuffer467     extern (C++) void writeUTF16(uint w) pure nothrow
468     {
469         reserve(4);
470         if (w <= 0xFFFF)
471         {
472             *cast(ushort*)(this.data.ptr + offset) = cast(ushort)w;
473             offset += 2;
474         }
475         else if (w <= 0x10FFFF)
476         {
477             *cast(ushort*)(this.data.ptr + offset) = cast(ushort)((w >> 10) + 0xD7C0);
478             *cast(ushort*)(this.data.ptr + offset + 2) = cast(ushort)((w & 0x3FF) | 0xDC00);
479             offset += 4;
480         }
481         else
482             assert(0);
483     }
484 
write4OutBuffer485     extern (C++) void write4(uint w) pure nothrow
486     {
487         version (Windows)
488         {
489             bool notnewline = w != 0x000A000D;
490         }
491         else
492         {
493             bool notnewline = true;
494         }
495         if (doindent && !notlinehead && notnewline)
496             indent();
497         reserve(4);
498         *cast(uint*)(this.data.ptr + offset) = w;
499         offset += 4;
500     }
501 
writeOutBuffer502     extern (C++) void write(const OutBuffer* buf) pure nothrow
503     {
504         if (buf)
505         {
506             reserve(buf.offset);
507             memcpy(data.ptr + offset, buf.data.ptr, buf.offset);
508             offset += buf.offset;
509         }
510     }
511 
fill0OutBuffer512     extern (C++) void fill0(size_t nbytes) pure nothrow
513     {
514         reserve(nbytes);
515         memset(data.ptr + offset, 0, nbytes);
516         offset += nbytes;
517     }
518 
519     /**
520      * Allocate space, but leave it uninitialized.
521      * Params:
522      *  nbytes = amount to allocate
523      * Returns:
524      *  slice of the allocated space to be filled in
525      */
allocateOutBuffer526     extern (D) char[] allocate(size_t nbytes) pure nothrow
527     {
528         reserve(nbytes);
529         offset += nbytes;
530         return cast(char[])data[offset - nbytes .. offset];
531     }
532 
vprintfOutBuffer533     extern (C++) void vprintf(const(char)* format, va_list args) nothrow
534     {
535         int count;
536         if (doindent && !notlinehead)
537             indent();
538         uint psize = 128;
539         for (;;)
540         {
541             reserve(psize);
542             va_list va;
543             va_copy(va, args);
544             /*
545                 The functions vprintf(), vfprintf(), vsprintf(), vsnprintf()
546                 are equivalent to the functions printf(), fprintf(), sprintf(),
547                 snprintf(), respectively, except that they are called with a
548                 va_list instead of a variable number of arguments. These
549                 functions do not call the va_end macro. Consequently, the value
550                 of ap is undefined after the call. The application should call
551                 va_end(ap) itself afterwards.
552                 */
553             count = vsnprintf(cast(char*)data.ptr + offset, psize, format, va);
554             va_end(va);
555             if (count == -1) // snn.lib and older libcmt.lib return -1 if buffer too small
556                 psize *= 2;
557             else if (count >= psize)
558                 psize = count + 1;
559             else
560                 break;
561         }
562         offset += count;
563         // if (mem.isGCEnabled)
564              memset(data.ptr + offset, 0xff, psize - count);
565     }
566 
567     static if (__VERSION__ < 2092)
568     {
printfOutBuffer569         extern (C++) void printf(const(char)* format, ...) nothrow
570         {
571             va_list ap;
572             va_start(ap, format);
573             vprintf(format, ap);
574             va_end(ap);
575         }
576     }
577     else
578     {
pragmaOutBuffer579         pragma(printf) extern (C++) void printf(const(char)* format, ...) nothrow
580         {
581             va_list ap;
582             va_start(ap, format);
583             vprintf(format, ap);
584             va_end(ap);
585         }
586     }
587 
588     /**************************************
589      * Convert `u` to a string and append it to the buffer.
590      * Params:
591      *  u = integral value to append
592      */
printOutBuffer593     extern (C++) void print(ulong u) pure nothrow
594     {
595         UnsignedStringBuf buf = void;
596         writestring(unsignedToTempString(u, buf));
597     }
598 
bracketOutBuffer599     extern (C++) void bracket(char left, char right) pure nothrow
600     {
601         reserve(2);
602         memmove(data.ptr + 1, data.ptr, offset);
603         data[0] = left;
604         data[offset + 1] = right;
605         offset += 2;
606     }
607 
608     /******************
609      * Insert left at i, and right at j.
610      * Return index just past right.
611      */
bracketOutBuffer612     extern (C++) size_t bracket(size_t i, const(char)* left, size_t j, const(char)* right) pure nothrow
613     {
614         size_t leftlen = strlen(left);
615         size_t rightlen = strlen(right);
616         reserve(leftlen + rightlen);
617         insert(i, left, leftlen);
618         insert(j + leftlen, right, rightlen);
619         return j + leftlen + rightlen;
620     }
621 
spreadOutBuffer622     extern (C++) void spread(size_t offset, size_t nbytes) pure nothrow
623     {
624         reserve(nbytes);
625         memmove(data.ptr + offset + nbytes, data.ptr + offset, this.offset - offset);
626         this.offset += nbytes;
627     }
628 
629     /****************************************
630      * Returns: offset + nbytes
631      */
insertOutBuffer632     extern (C++) size_t insert(size_t offset, const(void)* p, size_t nbytes) pure nothrow
633     {
634         spread(offset, nbytes);
635         memmove(data.ptr + offset, p, nbytes);
636         return offset + nbytes;
637     }
638 
insertOutBuffer639     size_t insert(size_t offset, const(char)[] s) pure nothrow
640     {
641         return insert(offset, s.ptr, s.length);
642     }
643 
removeOutBuffer644     extern (C++) void remove(size_t offset, size_t nbytes) pure nothrow @nogc
645     {
646         memmove(data.ptr + offset, data.ptr + offset + nbytes, this.offset - (offset + nbytes));
647         this.offset -= nbytes;
648     }
649 
650     /**
651      * Returns:
652      *   a non-owning const slice of the buffer contents
653      */
opSliceOutBuffer654     extern (D) const(char)[] opSlice() const pure nothrow @nogc @safe
655     {
656         return cast(const(char)[])data[0 .. offset];
657     }
658 
opSliceOutBuffer659     extern (D) const(char)[] opSlice(size_t lwr, size_t upr) const pure nothrow @nogc @safe
660     {
661         return cast(const(char)[])data[lwr .. upr];
662     }
663 
opIndexOutBuffer664     extern (D) char opIndex(size_t i) const pure nothrow @nogc @safe
665     {
666         return cast(char)data[i];
667     }
668 
669     alias opDollar = length;
670 
671     /***********************************
672      * Extract the data as a slice and take ownership of it.
673      *
674      * When `true` is passed as an argument, this function behaves
675      * like `dmd.utils.toDString(thisbuffer.extractChars())`.
676      *
677      * Params:
678      *   nullTerminate = When `true`, the data will be `null` terminated.
679      *                   This is useful to call C functions or store
680      *                   the result in `Strings`. Defaults to `false`.
681      */
682     extern (D) char[] extractSlice(bool nullTerminate = false) pure nothrow
683     {
684         const length = offset;
685         if (!nullTerminate)
686             return extractData()[0 .. length];
687         // There's already a terminating `'\0'`
688         if (length && data[length - 1] == '\0')
689             return extractData()[0 .. length - 1];
690         writeByte(0);
691         return extractData()[0 .. length];
692     }
693 
694     extern (D) byte[] extractUbyteSlice(bool nullTerminate = false) pure nothrow
695     {
696         return cast(byte[]) extractSlice(nullTerminate);
697     }
698 
699     // Append terminating null if necessary and get view of internal buffer
peekCharsOutBuffer700     extern (C++) char* peekChars() pure nothrow
701     {
702         if (!offset || data[offset - 1] != '\0')
703         {
704             writeByte(0);
705             offset--; // allow appending more
706         }
707         return cast(char*)data.ptr;
708     }
709 
710     // Append terminating null if necessary and take ownership of data
extractCharsOutBuffer711     extern (C++) char* extractChars() pure nothrow
712     {
713         if (!offset || data[offset - 1] != '\0')
714             writeByte(0);
715         return extractData();
716     }
717 
writesLEB128OutBuffer718     void writesLEB128(int value) pure nothrow
719     {
720         while (1)
721         {
722             ubyte b = value & 0x7F;
723 
724             value >>= 7;            // arithmetic right shift
725             if ((value == 0 && !(b & 0x40)) ||
726                 (value == -1 && (b & 0x40)))
727             {
728                  writeByte(b);
729                  break;
730             }
731             writeByte(b | 0x80);
732         }
733     }
734 
writeuLEB128OutBuffer735     void writeuLEB128(uint value) pure nothrow
736     {
737         do
738         {
739             ubyte b = value & 0x7F;
740 
741             value >>= 7;
742             if (value)
743                 b |= 0x80;
744             writeByte(b);
745         } while (value);
746     }
747 
748     /**
749     Destructively saves the contents of `this` to `filename`. As an
750     optimization, if the file already has identical contents with the buffer,
751     no copying is done. This is because on SSD drives reading is often much
752     faster than writing and because there's a high likelihood an identical
753     file is written during the build process.
754 
755     Params:
756     filename = the name of the file to receive the contents
757 
758     Returns: `true` iff the operation succeeded.
759     */
moveToFileOutBuffer760     extern(D) bool moveToFile(const char* filename)
761     {
762         bool result = true;
763         const bool identical = this[] == FileMapping!(const ubyte)(filename)[];
764 
765         if (fileMapping && fileMapping.active)
766         {
767             // Defer to corresponding functions in FileMapping.
768             if (identical)
769             {
770                 result = fileMapping.discard();
771             }
772             else
773             {
774                 // Resize to fit to get rid of the slack bytes at the end
775                 fileMapping.resize(offset);
776                 result = fileMapping.moveToFile(filename);
777             }
778             // Can't call destroy() here because the file mapping is already closed.
779             data = null;
780             offset = 0;
781         }
782         else
783         {
784             if (!identical)
785                 writeFile(filename, this[]);
786             destroy();
787         }
788 
789         return identical
790             ? result && touchFile(filename)
791             : result;
792     }
793 }
794 
795 /****** copied from core.internal.string *************/
796 
797 private:
798 
799 alias UnsignedStringBuf = char[20];
800 
801 char[] unsignedToTempString(ulong value, char[] buf, uint radix = 10) @safe pure nothrow @nogc
802 {
803     size_t i = buf.length;
804     do
805     {
806         if (value < radix)
807         {
808             ubyte x = cast(ubyte)value;
809             buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a');
810             break;
811         }
812         else
813         {
814             ubyte x = cast(ubyte)(value % radix);
815             value /= radix;
816             buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a');
817         }
818     } while (value);
819     return buf[i .. $];
820 }
821 
822 /************* unit tests **************************************************/
823 
824 unittest
825 {
826     OutBuffer buf;
827     buf.printf("betty");
828     buf.insert(1, "xx".ptr, 2);
829     buf.insert(3, "yy");
830     buf.remove(4, 1);
831     buf.bracket('(', ')');
832     const char[] s = buf[];
833     assert(s == "(bxxyetty)");
834     buf.destroy();
835 }
836 
837 unittest
838 {
839     OutBuffer buf;
840     buf.writestring("abc".ptr);
841     buf.prependstring("def");
842     buf.prependbyte('x');
843     OutBuffer buf2;
844     buf2.writestring("mmm");
845     buf.write(&buf2);
846     char[] s = buf.extractSlice();
847     assert(s == "xdefabcmmm");
848 }
849 
850 unittest
851 {
852     OutBuffer buf;
853     buf.writeByte('a');
854     char[] s = buf.extractSlice();
855     assert(s == "a");
856 
857     buf.writeByte('b');
858     char[] t = buf.extractSlice();
859     assert(t == "b");
860 }
861 
862 unittest
863 {
864     OutBuffer buf;
865     char* p = buf.peekChars();
866     assert(*p == 0);
867 
868     buf.writeByte('s');
869     char* q = buf.peekChars();
870     assert(strcmp(q, "s") == 0);
871 }
872 
873 unittest
874 {
875     char[10] buf;
876     char[] s = unsignedToTempString(278, buf[], 10);
877     assert(s == "278");
878 
879     s = unsignedToTempString(1, buf[], 10);
880     assert(s == "1");
881 
882     s = unsignedToTempString(8, buf[], 2);
883     assert(s == "1000");
884 
885     s = unsignedToTempString(29, buf[], 16);
886     assert(s == "1d");
887 }
888 
889 unittest
890 {
891     OutBuffer buf;
892     buf.writeUTF8(0x0000_0011);
893     buf.writeUTF8(0x0000_0111);
894     buf.writeUTF8(0x0000_1111);
895     buf.writeUTF8(0x0001_1111);
896     buf.writeUTF8(0x0010_0000);
897     assert(buf[] == "\x11\U00000111\U00001111\U00011111\U00100000");
898 
899     buf.reset();
900     buf.writeUTF16(0x0000_0011);
901     buf.writeUTF16(0x0010_FFFF);
902     assert(buf[] == cast(string) "\u0011\U0010FFFF"w);
903 }
904 
905 unittest
906 {
907     OutBuffer buf;
908     buf.doindent = true;
909 
910     const(char)[] s = "abc";
911     buf.writestring(s);
912     buf.level += 1;
913     buf.indent();
914     buf.writestring("abs");
915 
916     assert(buf[] == "abc\tabs");
917 
918     buf.setsize(4);
919     assert(buf.length == 4);
920 }
921