1 /**
2  * An expandable buffer in which you can write text or binary data.
3  *
4  * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved
5  * Authors:   Walter Bright, http://www.digitalmars.com
6  * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7  * Source:    $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/root/outbuffer.d, root/_outbuffer.d)
8  * Documentation: https://dlang.org/phobos/dmd_root_outbuffer.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/root/outbuffer.d
10  */
11 
12 module dmd.root.outbuffer;
13 
14 import core.stdc.stdio;
15 import core.stdc.string;
16 import dmd.root.stdarg;
17 import dmd.root.rmem;
18 import dmd.root.rootobject;
19 import dmd.root.string;
20 
21 debug
22 {
23     debug = stomp; // flush out dangling pointer problems by stomping on unused memory
24 }
25 
26 private extern (C) pure nothrow @nogc @system
27 {
28     version (CRuntime_DigitalMars)
29     {
30         int _vsnprintf(scope char* s, size_t n, scope const char* format,
31             va_list arg);
32         alias _vsnprintf vsnprintf;
33     }
34 
35     else version (CRuntime_Microsoft)
36     {
37         version (MinGW)
38         {
39             int __mingw_vsnprintf(scope char* s, size_t n,
40                 scope const char* format, va_list arg);
41             alias __mingw_vsnprintf vsnprintf;
42         }
43 
44         else
45             int vsnprintf(scope char* s, size_t n, scope const char* format,
46                 va_list arg);
47     }
48 
49     else version (Posix)
50         int vsnprintf(scope char* s, size_t n, scope const char* format,
51             va_list arg);
52     else
53         static assert( false, "Unsupported platform" );
54 }
55 
56 struct OutBuffer
57 {
58     private ubyte[] data;
59     private size_t offset;
60     private bool notlinehead;
61 
62     /// Whether to indent
63     bool doindent;
64     /// Whether to indent by 4 spaces or by tabs;
65     bool spaces;
66     /// Current indent level
67     int level;
68 
69     extern (C++) ~this() pure nothrow
70     {
71         debug (stomp) memset(data.ptr, 0xFF, data.length);
72         mem.xfree(data.ptr);
73     }
74 
75     extern (C++) size_t length() const pure @nogc @safe nothrow { return offset; }
76 
77     /**********************
78      * Transfer ownership of the allocated data to the caller.
79      * Returns:
80      *  pointer to the allocated data
81      */
82     extern (C++) char* extractData() pure nothrow @nogc @trusted
83     {
84         char* p = cast(char*)data.ptr;
85         data = null;
86         offset = 0;
87         return p;
88     }
89 
90     extern (C++) void destroy() pure nothrow @trusted
91     {
92         debug (stomp) memset(data.ptr, 0xFF, data.length);
93         mem.xfree(extractData());
94     }
95 
96     extern (C++) void reserve(size_t nbytes) pure nothrow
97     {
98         //debug (stomp) printf("OutBuffer::reserve: size = %lld, offset = %lld, nbytes = %lld\n", data.length, offset, nbytes);
99         if (data.length - offset < nbytes)
100         {
101             /* Increase by factor of 1.5; round up to 16 bytes.
102              * The odd formulation is so it will map onto single x86 LEA instruction.
103              */
104             const size = (((offset + nbytes) * 3 + 30) / 2) & ~15;
105 
106             debug (stomp)
107             {
108                 auto p = cast(ubyte*)mem.xmalloc(size);
109                 memcpy(p, data.ptr, offset);
110                 memset(data.ptr, 0xFF, data.length);  // stomp old location
111                 mem.xfree(data.ptr);
112                 memset(p + offset, 0xff, size - offset); // stomp unused data
113             }
114             else
115             {
116                 auto p = cast(ubyte*)mem.xrealloc(data.ptr, size);
117                 if (mem.isGCEnabled) // clear currently unused data to avoid false pointers
118                     memset(p + offset + nbytes, 0xff, size - offset - nbytes);
119             }
120             data = p[0 .. size];
121         }
122     }
123 
124     /************************
125      * Shrink the size of the data to `size`.
126      * Params:
127      *  size = new size of data, must be <= `.length`
128      */
129     extern (C++) void setsize(size_t size) pure nothrow @nogc @safe
130     {
131         assert(size <= offset);
132         offset = size;
133     }
134 
135     extern (C++) void reset() pure nothrow @nogc @safe
136     {
137         offset = 0;
138     }
139 
140     private void indent() pure nothrow
141     {
142         if (level)
143         {
144             const indentLevel = spaces ? level * 4 : level;
145             reserve(indentLevel);
146             data[offset .. offset + indentLevel] = (spaces ? ' ' : '\t');
147             offset += indentLevel;
148         }
149         notlinehead = true;
150     }
151 
152     extern (C++) void write(const(void)* data, size_t nbytes) pure nothrow
153     {
154         write(data[0 .. nbytes]);
155     }
156 
157     void write(const(void)[] buf) pure nothrow
158     {
159         if (doindent && !notlinehead)
160             indent();
161         reserve(buf.length);
162         memcpy(this.data.ptr + offset, buf.ptr, buf.length);
163         offset += buf.length;
164     }
165 
166     extern (C++) void writestring(const(char)* string) pure nothrow
167     {
168         write(string.toDString);
169     }
170 
171     void writestring(const(char)[] s) pure nothrow
172     {
173         write(s);
174     }
175 
176     void writestring(string s) pure nothrow
177     {
178         write(s);
179     }
180 
181     void writestringln(const(char)[] s)
182     {
183         writestring(s);
184         writenl();
185     }
186 
187     extern (C++) void prependstring(const(char)* string) pure nothrow
188     {
189         size_t len = strlen(string);
190         reserve(len);
191         memmove(data.ptr + len, data.ptr, offset);
192         memcpy(data.ptr, string, len);
193         offset += len;
194     }
195 
196     /// write newline
197     extern (C++) void writenl() pure nothrow
198     {
199         version (Windows)
200         {
201             writeword(0x0A0D); // newline is CR,LF on Microsoft OS's
202         }
203         else
204         {
205             writeByte('\n');
206         }
207         if (doindent)
208             notlinehead = false;
209     }
210 
211     extern (C++) void writeByte(uint b) pure nothrow
212     {
213         if (doindent && !notlinehead && b != '\n')
214             indent();
215         reserve(1);
216         this.data[offset] = cast(ubyte)b;
217         offset++;
218     }
219 
220     extern (C++) void writeUTF8(uint b) pure nothrow
221     {
222         reserve(6);
223         if (b <= 0x7F)
224         {
225             this.data[offset] = cast(ubyte)b;
226             offset++;
227         }
228         else if (b <= 0x7FF)
229         {
230             this.data[offset + 0] = cast(ubyte)((b >> 6) | 0xC0);
231             this.data[offset + 1] = cast(ubyte)((b & 0x3F) | 0x80);
232             offset += 2;
233         }
234         else if (b <= 0xFFFF)
235         {
236             this.data[offset + 0] = cast(ubyte)((b >> 12) | 0xE0);
237             this.data[offset + 1] = cast(ubyte)(((b >> 6) & 0x3F) | 0x80);
238             this.data[offset + 2] = cast(ubyte)((b & 0x3F) | 0x80);
239             offset += 3;
240         }
241         else if (b <= 0x1FFFFF)
242         {
243             this.data[offset + 0] = cast(ubyte)((b >> 18) | 0xF0);
244             this.data[offset + 1] = cast(ubyte)(((b >> 12) & 0x3F) | 0x80);
245             this.data[offset + 2] = cast(ubyte)(((b >> 6) & 0x3F) | 0x80);
246             this.data[offset + 3] = cast(ubyte)((b & 0x3F) | 0x80);
247             offset += 4;
248         }
249         else
250             assert(0);
251     }
252 
253     extern (C++) void prependbyte(uint b) pure nothrow
254     {
255         reserve(1);
256         memmove(data.ptr + 1, data.ptr, offset);
257         data[0] = cast(ubyte)b;
258         offset++;
259     }
260 
261     extern (C++) void writewchar(uint w) pure nothrow
262     {
263         version (Windows)
264         {
265             writeword(w);
266         }
267         else
268         {
269             write4(w);
270         }
271     }
272 
273     extern (C++) void writeword(uint w) pure nothrow
274     {
275         version (Windows)
276         {
277             uint newline = 0x0A0D;
278         }
279         else
280         {
281             uint newline = '\n';
282         }
283         if (doindent && !notlinehead && w != newline)
284             indent();
285 
286         reserve(2);
287         *cast(ushort*)(this.data.ptr + offset) = cast(ushort)w;
288         offset += 2;
289     }
290 
291     extern (C++) void writeUTF16(uint w) pure nothrow
292     {
293         reserve(4);
294         if (w <= 0xFFFF)
295         {
296             *cast(ushort*)(this.data.ptr + offset) = cast(ushort)w;
297             offset += 2;
298         }
299         else if (w <= 0x10FFFF)
300         {
301             *cast(ushort*)(this.data.ptr + offset) = cast(ushort)((w >> 10) + 0xD7C0);
302             *cast(ushort*)(this.data.ptr + offset + 2) = cast(ushort)((w & 0x3FF) | 0xDC00);
303             offset += 4;
304         }
305         else
306             assert(0);
307     }
308 
309     extern (C++) void write4(uint w) pure nothrow
310     {
311         version (Windows)
312         {
313             bool notnewline = w != 0x000A000D;
314         }
315         else
316         {
317             bool notnewline = true;
318         }
319         if (doindent && !notlinehead && notnewline)
320             indent();
321         reserve(4);
322         *cast(uint*)(this.data.ptr + offset) = w;
323         offset += 4;
324     }
325 
326     extern (C++) void write(const OutBuffer* buf) pure nothrow
327     {
328         if (buf)
329         {
330             reserve(buf.offset);
331             memcpy(data.ptr + offset, buf.data.ptr, buf.offset);
332             offset += buf.offset;
333         }
334     }
335 
336     extern (C++) void write(RootObject obj) /*nothrow*/
337     {
338         if (obj)
339         {
340             writestring(obj.toChars());
341         }
342     }
343 
344     extern (C++) void fill0(size_t nbytes) pure nothrow
345     {
346         reserve(nbytes);
347         memset(data.ptr + offset, 0, nbytes);
348         offset += nbytes;
349     }
350 
351     /**
352      * Allocate space, but leave it uninitialized.
353      * Params:
354      *  nbytes = amount to allocate
355      * Returns:
356      *  slice of the allocated space to be filled in
357      */
358     extern (D) char[] allocate(size_t nbytes) pure nothrow
359     {
360         reserve(nbytes);
361         offset += nbytes;
362         return cast(char[])data[offset - nbytes .. offset];
363     }
364 
365     extern (C++) void vprintf(scope const(char)* format, va_list args) pure nothrow
366     {
367         int count;
368         if (doindent && !notlinehead)
369             indent();
370         uint psize = 128;
371         for (;;)
372         {
373             reserve(psize);
374             va_list va;
375             va_copy(va, args);
376             /*
377                 The functions vprintf(), vfprintf(), vsprintf(), vsnprintf()
378                 are equivalent to the functions printf(), fprintf(), sprintf(),
379                 snprintf(), respectively, except that they are called with a
380                 va_list instead of a variable number of arguments. These
381                 functions do not call the va_end macro. Consequently, the value
382                 of ap is undefined after the call. The application should call
383                 va_end(ap) itself afterwards.
384                 */
385             count = vsnprintf(cast(char*)data.ptr + offset, psize, format, va);
386             va_end(va);
387             if (count == -1) // snn.lib and older libcmt.lib return -1 if buffer too small
388                 psize *= 2;
389             else if (count >= psize)
390                 psize = count + 1;
391             else
392                 break;
393         }
394         offset += count;
395         if (mem.isGCEnabled)
396             memset(data.ptr + offset, 0xff, psize - count);
397     }
398 
399     static if (__VERSION__ < 2092)
400     {
401         extern (C++) void printf(const(char)* format, ...) nothrow
402         {
403             va_list ap;
404             va_start(ap, format);
405             vprintf(format, ap);
406             va_end(ap);
407         }
408     }
409     else
410     {
411         pragma(printf) extern (C++) void printf(const(char)* format, ...) nothrow
412         {
413             va_list ap;
414             va_start(ap, format);
415             vprintf(format, ap);
416             va_end(ap);
417         }
418     }
419 
420     /**************************************
421      * Convert `u` to a string and append it to the buffer.
422      * Params:
423      *  u = integral value to append
424      */
425     extern (C++) void print(ulong u) pure nothrow
426     {
427         //import core.internal.string;  // not available
428         UnsignedStringBuf buf = void;
429         writestring(unsignedToTempString(u, buf));
430     }
431 
432     extern (C++) void bracket(char left, char right) pure nothrow
433     {
434         reserve(2);
435         memmove(data.ptr + 1, data.ptr, offset);
436         data[0] = left;
437         data[offset + 1] = right;
438         offset += 2;
439     }
440 
441     /******************
442      * Insert left at i, and right at j.
443      * Return index just past right.
444      */
445     extern (C++) size_t bracket(size_t i, const(char)* left, size_t j, const(char)* right) pure nothrow
446     {
447         size_t leftlen = strlen(left);
448         size_t rightlen = strlen(right);
449         reserve(leftlen + rightlen);
450         insert(i, left, leftlen);
451         insert(j + leftlen, right, rightlen);
452         return j + leftlen + rightlen;
453     }
454 
455     extern (C++) void spread(size_t offset, size_t nbytes) pure nothrow
456     {
457         reserve(nbytes);
458         memmove(data.ptr + offset + nbytes, data.ptr + offset, this.offset - offset);
459         this.offset += nbytes;
460     }
461 
462     /****************************************
463      * Returns: offset + nbytes
464      */
465     extern (C++) size_t insert(size_t offset, const(void)* p, size_t nbytes) pure nothrow
466     {
467         spread(offset, nbytes);
468         memmove(data.ptr + offset, p, nbytes);
469         return offset + nbytes;
470     }
471 
472     size_t insert(size_t offset, const(char)[] s) pure nothrow
473     {
474         return insert(offset, s.ptr, s.length);
475     }
476 
477     extern (C++) void remove(size_t offset, size_t nbytes) pure nothrow @nogc
478     {
479         memmove(data.ptr + offset, data.ptr + offset + nbytes, this.offset - (offset + nbytes));
480         this.offset -= nbytes;
481     }
482 
483     /**
484      * Returns:
485      *   a non-owning const slice of the buffer contents
486      */
487     extern (D) const(char)[] opSlice() const pure nothrow @nogc
488     {
489         return cast(const(char)[])data[0 .. offset];
490     }
491 
492     extern (D) const(char)[] opSlice(size_t lwr, size_t upr) const pure nothrow @nogc
493     {
494         return cast(const(char)[])data[lwr .. upr];
495     }
496 
497     extern (D) char opIndex(size_t i) const pure nothrow @nogc
498     {
499         return cast(char)data[i];
500     }
501 
502     /***********************************
503      * Extract the data as a slice and take ownership of it.
504      *
505      * When `true` is passed as an argument, this function behaves
506      * like `dmd.utils.toDString(thisbuffer.extractChars())`.
507      *
508      * Params:
509      *   nullTerminate = When `true`, the data will be `null` terminated.
510      *                   This is useful to call C functions or store
511      *                   the result in `Strings`. Defaults to `false`.
512      */
513     extern (D) char[] extractSlice(bool nullTerminate = false) pure nothrow
514     {
515         const length = offset;
516         if (!nullTerminate)
517             return extractData()[0 .. length];
518         // There's already a terminating `'\0'`
519         if (length && data[length - 1] == '\0')
520             return extractData()[0 .. length - 1];
521         writeByte(0);
522         return extractData()[0 .. length];
523     }
524 
525     // Append terminating null if necessary and get view of internal buffer
526     extern (C++) char* peekChars() pure nothrow
527     {
528         if (!offset || data[offset - 1] != '\0')
529         {
530             writeByte(0);
531             offset--; // allow appending more
532         }
533         return cast(char*)data.ptr;
534     }
535 
536     // Append terminating null if necessary and take ownership of data
537     extern (C++) char* extractChars() pure nothrow
538     {
539         if (!offset || data[offset - 1] != '\0')
540             writeByte(0);
541         return extractData();
542     }
543 }
544 
545 /****** copied from core.internal.string *************/
546 
547 private:
548 
549 alias UnsignedStringBuf = char[20];
550 
551 char[] unsignedToTempString(ulong value, char[] buf, uint radix = 10) @safe pure nothrow @nogc
552 {
553     size_t i = buf.length;
554     do
555     {
556         if (value < radix)
557         {
558             ubyte x = cast(ubyte)value;
559             buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a');
560             break;
561         }
562         else
563         {
564             ubyte x = cast(ubyte)(value % radix);
565             value = value / radix;
566             buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a');
567         }
568     } while (value);
569     return buf[i .. $];
570 }
571 
572 /************* unit tests **************************************************/
573 
574 unittest
575 {
576     OutBuffer buf;
577     buf.printf("betty");
578     buf.insert(1, "xx".ptr, 2);
579     buf.insert(3, "yy");
580     buf.remove(4, 1);
581     buf.bracket('(', ')');
582     const char[] s = buf[];
583     assert(s == "(bxxyetty)");
584     buf.destroy();
585 }
586 
587 unittest
588 {
589     OutBuffer buf;
590     buf.writestring("abc".ptr);
591     buf.prependstring("def");
592     buf.prependbyte('x');
593     OutBuffer buf2;
594     buf2.writestring("mmm");
595     buf.write(&buf2);
596     char[] s = buf.extractSlice();
597     assert(s == "xdefabcmmm");
598 }
599 
600 unittest
601 {
602     OutBuffer buf;
603     buf.writeByte('a');
604     char[] s = buf.extractSlice();
605     assert(s == "a");
606 
607     buf.writeByte('b');
608     char[] t = buf.extractSlice();
609     assert(t == "b");
610 }
611 
612 unittest
613 {
614     OutBuffer buf;
615     char* p = buf.peekChars();
616     assert(*p == 0);
617 
618     buf.writeByte('s');
619     char* q = buf.peekChars();
620     assert(strcmp(q, "s") == 0);
621 }
622 
623 unittest
624 {
625     char[10] buf;
626     char[] s = unsignedToTempString(278, buf[], 10);
627     assert(s == "278");
628 
629     s = unsignedToTempString(1, buf[], 10);
630     assert(s == "1");
631 
632     s = unsignedToTempString(8, buf[], 2);
633     assert(s == "1000");
634 
635     s = unsignedToTempString(29, buf[], 16);
636     assert(s == "1d");
637 }