| // Written in the D programming language. | |
| /** | |
| Serialize data to `ubyte` arrays. | |
| * Copyright: Copyright The D Language Foundation 2000 - 2015. | |
| * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). | |
| * Authors: $(HTTP digitalmars.com, Walter Bright) | |
| * Source: $(PHOBOSSRC std/outbuffer.d) | |
| * | |
| * $(SCRIPT inhibitQuickIndex = 1;) | |
| */ | |
| module std.outbuffer; | |
| import core.stdc.stdarg; | |
| import std.traits : isSomeString; | |
| /********************************************* | |
| * OutBuffer provides a way to build up an array of bytes out | |
| * of raw data. It is useful for things like preparing an | |
| * array of bytes to write out to a file. | |
| * OutBuffer's byte order is the format native to the computer. | |
| * To control the byte order (endianness), use a class derived | |
| * from OutBuffer. | |
| * | |
| * OutBuffer's internal buffer is allocated with the GC. Pointers | |
| * stored into the buffer are scanned by the GC, but you have to | |
| * ensure proper alignment, e.g. by using `alignSize((void*).sizeof)`. | |
| */ | |
| class OutBuffer | |
| { | |
| ubyte[] data; | |
| size_t offset; | |
| invariant() | |
| { | |
| assert(offset <= data.length); | |
| } | |
| pure nothrow @safe | |
| { | |
| /********************************* | |
| * Convert to array of bytes. | |
| */ | |
| inout(ubyte)[] toBytes() scope inout { return data[0 .. offset]; } | |
| /*********************************** | |
| * Preallocate nbytes more to the size of the internal buffer. | |
| * | |
| * This is a | |
| * speed optimization, a good guess at the maximum size of the resulting | |
| * buffer will improve performance by eliminating reallocations and copying. | |
| */ | |
| void reserve(size_t nbytes) @trusted | |
| in | |
| { | |
| assert(offset + nbytes >= offset); | |
| } | |
| out | |
| { | |
| assert(offset + nbytes <= data.length); | |
| } | |
| do | |
| { | |
| if (data.length < offset + nbytes) | |
| { | |
| void[] vdata = data; | |
| vdata.length = (offset + nbytes + 7) * 2; // allocates as void[] to not set BlkAttr.NO_SCAN | |
| data = cast(ubyte[]) vdata; | |
| } | |
| } | |
| /********************************** | |
| * put enables OutBuffer to be used as an OutputRange. | |
| */ | |
| alias put = write; | |
| /************************************* | |
| * Append data to the internal buffer. | |
| */ | |
| void write(scope const(ubyte)[] bytes) | |
| { | |
| reserve(bytes.length); | |
| data[offset .. offset + bytes.length] = bytes[]; | |
| offset += bytes.length; | |
| } | |
| void write(scope const(wchar)[] chars) @trusted | |
| { | |
| write(cast(ubyte[]) chars); | |
| } | |
| void write(scope const(dchar)[] chars) @trusted | |
| { | |
| write(cast(ubyte[]) chars); | |
| } | |
| void write(ubyte b) /// ditto | |
| { | |
| reserve(ubyte.sizeof); | |
| this.data[offset] = b; | |
| offset += ubyte.sizeof; | |
| } | |
| void write(byte b) { write(cast(ubyte) b); } /// ditto | |
| void write(char c) { write(cast(ubyte) c); } /// ditto | |
| void write(dchar c) { write(cast(uint) c); } /// ditto | |
| void write(ushort w) @trusted /// ditto | |
| { | |
| reserve(ushort.sizeof); | |
| *cast(ushort *)&data[offset] = w; | |
| offset += ushort.sizeof; | |
| } | |
| void write(short s) { write(cast(ushort) s); } /// ditto | |
| void write(wchar c) @trusted /// ditto | |
| { | |
| reserve(wchar.sizeof); | |
| *cast(wchar *)&data[offset] = c; | |
| offset += wchar.sizeof; | |
| } | |
| void write(uint w) @trusted /// ditto | |
| { | |
| reserve(uint.sizeof); | |
| *cast(uint *)&data[offset] = w; | |
| offset += uint.sizeof; | |
| } | |
| void write(int i) { write(cast(uint) i); } /// ditto | |
| void write(ulong l) @trusted /// ditto | |
| { | |
| reserve(ulong.sizeof); | |
| *cast(ulong *)&data[offset] = l; | |
| offset += ulong.sizeof; | |
| } | |
| void write(long l) { write(cast(ulong) l); } /// ditto | |
| void write(float f) @trusted /// ditto | |
| { | |
| reserve(float.sizeof); | |
| *cast(float *)&data[offset] = f; | |
| offset += float.sizeof; | |
| } | |
| void write(double f) @trusted /// ditto | |
| { | |
| reserve(double.sizeof); | |
| *cast(double *)&data[offset] = f; | |
| offset += double.sizeof; | |
| } | |
| void write(real f) @trusted /// ditto | |
| { | |
| reserve(real.sizeof); | |
| *cast(real *)&data[offset] = f; | |
| offset += real.sizeof; | |
| } | |
| void write(scope const(char)[] s) @trusted /// ditto | |
| { | |
| write(cast(ubyte[]) s); | |
| } | |
| void write(scope const OutBuffer buf) /// ditto | |
| { | |
| write(buf.toBytes()); | |
| } | |
| /**************************************** | |
| * Append nbytes of val to the internal buffer. | |
| * Params: | |
| * nbytes = Number of bytes to fill. | |
| * val = Value to fill, defaults to 0. | |
| */ | |
| void fill(size_t nbytes, ubyte val = 0) | |
| { | |
| reserve(nbytes); | |
| data[offset .. offset + nbytes] = val; | |
| offset += nbytes; | |
| } | |
| /**************************************** | |
| * Append nbytes of 0 to the internal buffer. | |
| * Param: | |
| * nbytes - number of bytes to fill. | |
| */ | |
| void fill0(size_t nbytes) | |
| { | |
| fill(nbytes); | |
| } | |
| /********************************** | |
| * Append bytes until the buffer aligns on a power of 2 boundary. | |
| * | |
| * By default fills with 0 bytes. | |
| * | |
| * Params: | |
| * alignsize = Alignment value. Must be power of 2. | |
| * val = Value to fill, defaults to 0. | |
| */ | |
| void alignSize(size_t alignsize, ubyte val = 0) | |
| in | |
| { | |
| assert(alignsize && (alignsize & (alignsize - 1)) == 0); | |
| } | |
| out | |
| { | |
| assert((offset & (alignsize - 1)) == 0); | |
| } | |
| do | |
| { | |
| auto nbytes = offset & (alignsize - 1); | |
| if (nbytes) | |
| fill(alignsize - nbytes, val); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| OutBuffer buf = new OutBuffer(); | |
| buf.write(cast(ubyte) 1); | |
| buf.align2(); | |
| assert(buf.toBytes() == "\x01\x00"); | |
| buf.write(cast(ubyte) 2); | |
| buf.align4(); | |
| assert(buf.toBytes() == "\x01\x00\x02\x00"); | |
| buf.write(cast(ubyte) 3); | |
| buf.alignSize(8); | |
| assert(buf.toBytes() == "\x01\x00\x02\x00\x03\x00\x00\x00"); | |
| } | |
| /// ditto | |
| @safe unittest | |
| { | |
| OutBuffer buf = new OutBuffer(); | |
| buf.write(cast(ubyte) 1); | |
| buf.align2(0x55); | |
| assert(buf.toBytes() == "\x01\x55"); | |
| buf.write(cast(ubyte) 2); | |
| buf.align4(0x55); | |
| assert(buf.toBytes() == "\x01\x55\x02\x55"); | |
| buf.write(cast(ubyte) 3); | |
| buf.alignSize(8, 0x55); | |
| assert(buf.toBytes() == "\x01\x55\x02\x55\x03\x55\x55\x55"); | |
| } | |
| /// Clear the data in the buffer | |
| void clear() | |
| { | |
| offset = 0; | |
| } | |
| /**************************************** | |
| * Optimize common special case alignSize(2) | |
| * Params: | |
| * val = Value to fill, defaults to 0. | |
| */ | |
| void align2(ubyte val = 0) | |
| { | |
| if (offset & 1) | |
| write(cast(byte) val); | |
| } | |
| /**************************************** | |
| * Optimize common special case alignSize(4) | |
| * Params: | |
| * val = Value to fill, defaults to 0. | |
| */ | |
| void align4(ubyte val = 0) | |
| { | |
| if (offset & 3) | |
| { auto nbytes = (4 - offset) & 3; | |
| fill(nbytes, val); | |
| } | |
| } | |
| /************************************** | |
| * Convert internal buffer to array of chars. | |
| */ | |
| override string toString() const | |
| { | |
| //printf("OutBuffer.toString()\n"); | |
| return cast(string) data[0 .. offset].idup; | |
| } | |
| } | |
| /***************************************** | |
| * Append output of C's vprintf() to internal buffer. | |
| */ | |
| void vprintf(scope string format, va_list args) @system nothrow | |
| { | |
| import core.stdc.stdio : vsnprintf; | |
| import core.stdc.stdlib : alloca; | |
| import std.string : toStringz; | |
| version (StdUnittest) | |
| char[3] buffer = void; // trigger reallocation | |
| else | |
| char[128] buffer = void; | |
| int count; | |
| // Can't use `tempCString()` here as it will result in compilation error: | |
| // "cannot mix core.std.stdlib.alloca() and exception handling". | |
| auto f = toStringz(format); | |
| auto p = buffer.ptr; | |
| auto psize = buffer.length; | |
| for (;;) | |
| { | |
| va_list args2; | |
| va_copy(args2, args); | |
| count = vsnprintf(p, psize, f, args2); | |
| va_end(args2); | |
| if (count == -1) | |
| { | |
| if (psize > psize.max / 2) assert(0); // overflow check | |
| psize *= 2; | |
| } | |
| else if (count >= psize) | |
| { | |
| if (count == count.max) assert(0); // overflow check | |
| psize = count + 1; | |
| } | |
| else | |
| break; | |
| p = cast(char *) alloca(psize); // buffer too small, try again with larger size | |
| } | |
| write(cast(ubyte[]) p[0 .. count]); | |
| } | |
| /***************************************** | |
| * Append output of C's printf() to internal buffer. | |
| */ | |
| void printf(scope string format, ...) @system | |
| { | |
| va_list ap; | |
| va_start(ap, format); | |
| vprintf(format, ap); | |
| va_end(ap); | |
| } | |
| /** | |
| * Formats and writes its arguments in text format to the OutBuffer. | |
| * | |
| * Params: | |
| * fmt = format string as described in $(REF formattedWrite, std,format) | |
| * args = arguments to be formatted | |
| * | |
| * See_Also: | |
| * $(REF _writef, std,stdio); | |
| * $(REF formattedWrite, std,format); | |
| */ | |
| void writef(Char, A...)(scope const(Char)[] fmt, A args) | |
| { | |
| import std.format.write : formattedWrite; | |
| formattedWrite(this, fmt, args); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| OutBuffer b = new OutBuffer(); | |
| b.writef("a%sb", 16); | |
| assert(b.toString() == "a16b"); | |
| } | |
| /// ditto | |
| void writef(alias fmt, A...)(A args) | |
| if (isSomeString!(typeof(fmt))) | |
| { | |
| import std.format : checkFormatException; | |
| alias e = checkFormatException!(fmt, A); | |
| static assert(!e, e); | |
| return this.writef(fmt, args); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| OutBuffer b = new OutBuffer(); | |
| b.writef!"a%sb"(16); | |
| assert(b.toString() == "a16b"); | |
| } | |
| /** | |
| * Formats and writes its arguments in text format to the OutBuffer, | |
| * followed by a newline. | |
| * | |
| * Params: | |
| * fmt = format string as described in $(REF formattedWrite, std,format) | |
| * args = arguments to be formatted | |
| * | |
| * See_Also: | |
| * $(REF _writefln, std,stdio); | |
| * $(REF formattedWrite, std,format); | |
| */ | |
| void writefln(Char, A...)(scope const(Char)[] fmt, A args) | |
| { | |
| import std.format.write : formattedWrite; | |
| formattedWrite(this, fmt, args); | |
| put('\n'); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| OutBuffer b = new OutBuffer(); | |
| b.writefln("a%sb", 16); | |
| assert(b.toString() == "a16b\n"); | |
| } | |
| /// ditto | |
| void writefln(alias fmt, A...)(A args) | |
| if (isSomeString!(typeof(fmt))) | |
| { | |
| import std.format : checkFormatException; | |
| alias e = checkFormatException!(fmt, A); | |
| static assert(!e, e); | |
| return this.writefln(fmt, args); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| OutBuffer b = new OutBuffer(); | |
| b.writefln!"a%sb"(16); | |
| assert(b.toString() == "a16b\n"); | |
| } | |
| /***************************************** | |
| * At offset index into buffer, create nbytes of space by shifting upwards | |
| * all data past index. | |
| */ | |
| void spread(size_t index, size_t nbytes) pure nothrow @safe | |
| in | |
| { | |
| assert(index <= offset); | |
| } | |
| do | |
| { | |
| reserve(nbytes); | |
| // This is an overlapping copy - should use memmove() | |
| for (size_t i = offset; i > index; ) | |
| { | |
| --i; | |
| data[i + nbytes] = data[i]; | |
| } | |
| offset += nbytes; | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.string : cmp; | |
| OutBuffer buf = new OutBuffer(); | |
| assert(buf.offset == 0); | |
| buf.write("hello"); | |
| buf.write(cast(byte) 0x20); | |
| buf.write("world"); | |
| buf.writef(" %d", 62665); | |
| assert(cmp(buf.toString(), "hello world 62665") == 0); | |
| buf.clear(); | |
| assert(cmp(buf.toString(), "") == 0); | |
| buf.write("New data"); | |
| assert(cmp(buf.toString(),"New data") == 0); | |
| } | |
| @safe unittest | |
| { | |
| import std.range; | |
| static assert(isOutputRange!(OutBuffer, char)); | |
| import std.algorithm; | |
| { | |
| OutBuffer buf = new OutBuffer(); | |
| "hello".copy(buf); | |
| assert(buf.toBytes() == "hello"); | |
| } | |
| { | |
| OutBuffer buf = new OutBuffer(); | |
| "hello"w.copy(buf); | |
| version (LittleEndian) | |
| assert(buf.toBytes() == "h\x00e\x00l\x00l\x00o\x00"); | |
| version (BigEndian) | |
| assert(buf.toBytes() == "\x00h\x00e\x00l\x00l\x00o"); | |
| } | |
| { | |
| OutBuffer buf = new OutBuffer(); | |
| "hello"d.copy(buf); | |
| version (LittleEndian) | |
| assert(buf.toBytes() == "h\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o\x00\x00\x00"); | |
| version (BigEndian) | |
| assert(buf.toBytes() == "\x00\x00\x00h\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o"); | |
| } | |
| } |