1// Written in the D programming language.
2
3/**
4Serialize data to $(D ubyte) arrays.
5
6 * Copyright: Copyright Digital Mars 2000 - 2015.
7 * License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
8 * Authors:   $(HTTP digitalmars.com, Walter Bright)
9 * Source:    $(PHOBOSSRC std/_outbuffer.d)
10 *
11 * $(SCRIPT inhibitQuickIndex = 1;)
12 */
13module std.outbuffer;
14
15import core.stdc.stdarg; // : va_list;
16
17/*********************************************
18 * OutBuffer provides a way to build up an array of bytes out
19 * of raw data. It is useful for things like preparing an
20 * array of bytes to write out to a file.
21 * OutBuffer's byte order is the format native to the computer.
22 * To control the byte order (endianness), use a class derived
23 * from OutBuffer.
24 * OutBuffer's internal buffer is allocated with the GC. Pointers
25 * stored into the buffer are scanned by the GC, but you have to
26 * ensure proper alignment, e.g. by using alignSize((void*).sizeof).
27 */
28
29class OutBuffer
30{
31    ubyte[] data;
32    size_t offset;
33
34    invariant()
35    {
36        assert(offset <= data.length);
37    }
38
39  pure nothrow @safe
40  {
41    /*********************************
42     * Convert to array of bytes.
43     */
44    ubyte[] toBytes() { return data[0 .. offset]; }
45
46    /***********************************
47     * Preallocate nbytes more to the size of the internal buffer.
48     *
49     * This is a
50     * speed optimization, a good guess at the maximum size of the resulting
51     * buffer will improve performance by eliminating reallocations and copying.
52     */
53    void reserve(size_t nbytes) @trusted
54        in
55        {
56            assert(offset + nbytes >= offset);
57        }
58        out
59        {
60            assert(offset + nbytes <= data.length);
61        }
62        body
63        {
64            if (data.length < offset + nbytes)
65            {
66                void[] vdata = data;
67                vdata.length = (offset + nbytes + 7) * 2; // allocates as void[] to not set BlkAttr.NO_SCAN
68                data = cast(ubyte[]) vdata;
69            }
70        }
71
72    /**********************************
73     * put enables OutBuffer to be used as an OutputRange.
74     */
75    alias put = write;
76
77    /*************************************
78     * Append data to the internal buffer.
79     */
80
81    void write(const(ubyte)[] bytes)
82        {
83            reserve(bytes.length);
84            data[offset .. offset + bytes.length] = bytes[];
85            offset += bytes.length;
86        }
87
88    void write(in wchar[] chars) @trusted
89        {
90        write(cast(ubyte[]) chars);
91        }
92
93    void write(const(dchar)[] chars) @trusted
94        {
95        write(cast(ubyte[]) chars);
96        }
97
98    void write(ubyte b)         /// ditto
99        {
100            reserve(ubyte.sizeof);
101            this.data[offset] = b;
102            offset += ubyte.sizeof;
103        }
104
105    void write(byte b) { write(cast(ubyte) b); }         /// ditto
106    void write(char c) { write(cast(ubyte) c); }         /// ditto
107    void write(dchar c) { write(cast(uint) c); }         /// ditto
108
109    void write(ushort w) @trusted                /// ditto
110    {
111        reserve(ushort.sizeof);
112        *cast(ushort *)&data[offset] = w;
113        offset += ushort.sizeof;
114    }
115
116    void write(short s) { write(cast(ushort) s); }               /// ditto
117
118    void write(wchar c) @trusted        /// ditto
119    {
120        reserve(wchar.sizeof);
121        *cast(wchar *)&data[offset] = c;
122        offset += wchar.sizeof;
123    }
124
125    void write(uint w) @trusted         /// ditto
126    {
127        reserve(uint.sizeof);
128        *cast(uint *)&data[offset] = w;
129        offset += uint.sizeof;
130    }
131
132    void write(int i) { write(cast(uint) i); }           /// ditto
133
134    void write(ulong l) @trusted         /// ditto
135    {
136        reserve(ulong.sizeof);
137        *cast(ulong *)&data[offset] = l;
138        offset += ulong.sizeof;
139    }
140
141    void write(long l) { write(cast(ulong) l); }         /// ditto
142
143    void write(float f) @trusted         /// ditto
144    {
145        reserve(float.sizeof);
146        *cast(float *)&data[offset] = f;
147        offset += float.sizeof;
148    }
149
150    void write(double f) @trusted               /// ditto
151    {
152        reserve(double.sizeof);
153        *cast(double *)&data[offset] = f;
154        offset += double.sizeof;
155    }
156
157    void write(real f) @trusted         /// ditto
158    {
159        reserve(real.sizeof);
160        *cast(real *)&data[offset] = f;
161        offset += real.sizeof;
162    }
163
164    void write(in char[] s) @trusted             /// ditto
165    {
166        write(cast(ubyte[]) s);
167    }
168
169    void write(OutBuffer buf)           /// ditto
170    {
171        write(buf.toBytes());
172    }
173
174    /****************************************
175     * Append nbytes of 0 to the internal buffer.
176     */
177
178    void fill0(size_t nbytes)
179    {
180        reserve(nbytes);
181        data[offset .. offset + nbytes] = 0;
182        offset += nbytes;
183    }
184
185    /**********************************
186     * 0-fill to align on power of 2 boundary.
187     */
188
189    void alignSize(size_t alignsize)
190    in
191    {
192        assert(alignsize && (alignsize & (alignsize - 1)) == 0);
193    }
194    out
195    {
196        assert((offset & (alignsize - 1)) == 0);
197    }
198    body
199    {
200        auto nbytes = offset & (alignsize - 1);
201        if (nbytes)
202            fill0(alignsize - nbytes);
203    }
204
205    /// Clear the data in the buffer
206    void clear()
207    {
208        offset = 0;
209    }
210
211    /****************************************
212     * Optimize common special case alignSize(2)
213     */
214
215    void align2()
216    {
217        if (offset & 1)
218            write(cast(byte) 0);
219    }
220
221    /****************************************
222     * Optimize common special case alignSize(4)
223     */
224
225    void align4()
226    {
227        if (offset & 3)
228        {   auto nbytes = (4 - offset) & 3;
229            fill0(nbytes);
230        }
231    }
232
233    /**************************************
234     * Convert internal buffer to array of chars.
235     */
236
237    override string toString() const
238    {
239        //printf("OutBuffer.toString()\n");
240        return cast(string) data[0 .. offset].idup;
241    }
242  }
243
244    /*****************************************
245     * Append output of C's vprintf() to internal buffer.
246     */
247
248    void vprintf(string format, va_list args) @trusted nothrow
249    {
250        import core.stdc.stdio : vsnprintf;
251        import core.stdc.stdlib : alloca;
252        import std.string : toStringz;
253
254        version (unittest)
255            char[3] buffer = void;      // trigger reallocation
256        else
257            char[128] buffer = void;
258        int count;
259
260        // Can't use `tempCString()` here as it will result in compilation error:
261        // "cannot mix core.std.stdlib.alloca() and exception handling".
262        auto f = toStringz(format);
263        auto p = buffer.ptr;
264        auto psize = buffer.length;
265        for (;;)
266        {
267            va_list args2;
268            va_copy(args2, args);
269            count = vsnprintf(p, psize, f, args2);
270            va_end(args2);
271            if (count == -1)
272            {
273                if (psize > psize.max / 2) assert(0); // overflow check
274                psize *= 2;
275            }
276            else if (count >= psize)
277            {
278                if (count == count.max) assert(0); // overflow check
279                psize = count + 1;
280            }
281            else
282                break;
283
284            p = cast(char *) alloca(psize); // buffer too small, try again with larger size
285        }
286        write(cast(ubyte[]) p[0 .. count]);
287    }
288
289    /*****************************************
290     * Append output of C's printf() to internal buffer.
291     */
292
293    void printf(string format, ...) @trusted
294    {
295        va_list ap;
296        va_start(ap, format);
297        vprintf(format, ap);
298        va_end(ap);
299    }
300
301    /**
302     * Formats and writes its arguments in text format to the OutBuffer.
303     *
304     * Params:
305     *  fmt = format string as described in $(REF formattedWrite, std,format)
306     *  args = arguments to be formatted
307     *
308     * See_Also:
309     *  $(REF _writef, std,stdio);
310     *  $(REF formattedWrite, std,format);
311     */
312    void writef(Char, A...)(in Char[] fmt, A args)
313    {
314        import std.format : formattedWrite;
315        formattedWrite(this, fmt, args);
316    }
317
318    ///
319    @safe unittest
320    {
321        OutBuffer b = new OutBuffer();
322        b.writef("a%sb", 16);
323        assert(b.toString() == "a16b");
324    }
325
326    /**
327     * Formats and writes its arguments in text format to the OutBuffer,
328     * followed by a newline.
329     *
330     * Params:
331     *  fmt = format string as described in $(REF formattedWrite, std,format)
332     *  args = arguments to be formatted
333     *
334     * See_Also:
335     *  $(REF _writefln, std,stdio);
336     *  $(REF formattedWrite, std,format);
337     */
338    void writefln(Char, A...)(in Char[] fmt, A args)
339    {
340        import std.format : formattedWrite;
341        formattedWrite(this, fmt, args);
342        put('\n');
343    }
344
345    ///
346    @safe unittest
347    {
348        OutBuffer b = new OutBuffer();
349        b.writefln("a%sb", 16);
350        assert(b.toString() == "a16b\n");
351    }
352
353    /*****************************************
354     * At offset index into buffer, create nbytes of space by shifting upwards
355     * all data past index.
356     */
357
358    void spread(size_t index, size_t nbytes) pure nothrow @safe
359        in
360        {
361            assert(index <= offset);
362        }
363        body
364        {
365            reserve(nbytes);
366
367            // This is an overlapping copy - should use memmove()
368            for (size_t i = offset; i > index; )
369            {
370                --i;
371                data[i + nbytes] = data[i];
372            }
373            offset += nbytes;
374        }
375}
376
377///
378@safe unittest
379{
380    import std.string : cmp;
381
382    OutBuffer buf = new OutBuffer();
383
384    assert(buf.offset == 0);
385    buf.write("hello");
386    buf.write(cast(byte) 0x20);
387    buf.write("world");
388    buf.printf(" %d", 62665);
389    assert(cmp(buf.toString(), "hello world 62665") == 0);
390
391    buf.clear();
392    assert(cmp(buf.toString(), "") == 0);
393    buf.write("New data");
394    assert(cmp(buf.toString(),"New data") == 0);
395}
396
397@safe unittest
398{
399    import std.range;
400    static assert(isOutputRange!(OutBuffer, char));
401
402    import std.algorithm;
403  {
404    OutBuffer buf = new OutBuffer();
405    "hello".copy(buf);
406    assert(buf.toBytes() == "hello");
407  }
408  {
409    OutBuffer buf = new OutBuffer();
410    "hello"w.copy(buf);
411    version (LittleEndian)
412        assert(buf.toBytes() == "h\x00e\x00l\x00l\x00o\x00");
413    version (BigEndian)
414        assert(buf.toBytes() == "\x00h\x00e\x00l\x00l\x00o");
415  }
416  {
417    OutBuffer buf = new OutBuffer();
418    "hello"d.copy(buf);
419    version (LittleEndian)
420        assert(buf.toBytes() == "h\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o\x00\x00\x00");
421    version (BigEndian)
422        assert(buf.toBytes() == "\x00\x00\x00h\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o");
423  }
424}
425