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