mirror of
https://github.com/dlang/phobos.git
synced 2025-04-30 07:00:37 +03:00

MS conforms to the standard beginning with VS 2015, i.e., if the buffer is too small, vsnprintf() returns the number of required characters (excl. terminating null). VS 2013 and earlier always returned -1 in that case. So just use the generic (previous POSIX) version, it's compatible with older VS anyway. Already unittested in line 380 ("hello world 6" vs. "hello world 62665").
404 lines
10 KiB
D
404 lines
10 KiB
D
// Written in the D programming language.
|
|
|
|
/**
|
|
Serialize data to $(D ubyte) arrays.
|
|
|
|
* Copyright: Copyright Digital Mars 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)
|
|
*/
|
|
module std.outbuffer;
|
|
|
|
import core.stdc.stdarg; // : va_list;
|
|
|
|
/*********************************************
|
|
* 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.
|
|
*/
|
|
ubyte[] toBytes() { 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);
|
|
}
|
|
body
|
|
{
|
|
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(const(ubyte)[] bytes)
|
|
{
|
|
reserve(bytes.length);
|
|
data[offset .. offset + bytes.length] = bytes[];
|
|
offset += bytes.length;
|
|
}
|
|
|
|
void write(in wchar[] chars) @trusted
|
|
{
|
|
write(cast(ubyte[]) chars);
|
|
}
|
|
|
|
void write(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(in char[] s) @trusted /// ditto
|
|
{
|
|
write(cast(ubyte[])s);
|
|
}
|
|
|
|
void write(OutBuffer buf) /// ditto
|
|
{
|
|
write(buf.toBytes());
|
|
}
|
|
|
|
/****************************************
|
|
* Append nbytes of 0 to the internal buffer.
|
|
*/
|
|
|
|
void fill0(size_t nbytes)
|
|
{
|
|
reserve(nbytes);
|
|
data[offset .. offset + nbytes] = 0;
|
|
offset += nbytes;
|
|
}
|
|
|
|
/**********************************
|
|
* 0-fill to align on power of 2 boundary.
|
|
*/
|
|
|
|
void alignSize(size_t alignsize)
|
|
in
|
|
{
|
|
assert(alignsize && (alignsize & (alignsize - 1)) == 0);
|
|
}
|
|
out
|
|
{
|
|
assert((offset & (alignsize - 1)) == 0);
|
|
}
|
|
body
|
|
{
|
|
auto nbytes = offset & (alignsize - 1);
|
|
if (nbytes)
|
|
fill0(alignsize - nbytes);
|
|
}
|
|
|
|
/****************************************
|
|
* Optimize common special case alignSize(2)
|
|
*/
|
|
|
|
void align2()
|
|
{
|
|
if (offset & 1)
|
|
write(cast(byte)0);
|
|
}
|
|
|
|
/****************************************
|
|
* Optimize common special case alignSize(4)
|
|
*/
|
|
|
|
void align4()
|
|
{
|
|
if (offset & 3)
|
|
{ auto nbytes = (4 - offset) & 3;
|
|
fill0(nbytes);
|
|
}
|
|
}
|
|
|
|
/**************************************
|
|
* 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(string format, va_list args) @trusted nothrow
|
|
{
|
|
import std.string : toStringz;
|
|
import core.stdc.stdio : vsnprintf;
|
|
import core.stdc.stdlib : alloca;
|
|
|
|
version (unittest)
|
|
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(string format, ...) @trusted
|
|
{
|
|
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...)(in Char[] fmt, A args)
|
|
{
|
|
import std.format : formattedWrite;
|
|
formattedWrite(this, 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...)(in Char[] fmt, A args)
|
|
{
|
|
import std.format : formattedWrite;
|
|
formattedWrite(this, fmt, args);
|
|
put('\n');
|
|
}
|
|
|
|
///
|
|
@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);
|
|
}
|
|
body
|
|
{
|
|
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.printf(" %d", 62665);
|
|
assert(cmp(buf.toString(), "hello world 62665") == 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);
|
|
assert(buf.toBytes() == "h\x00e\x00l\x00l\x00o\x00");
|
|
}
|
|
{
|
|
OutBuffer buf = new OutBuffer();
|
|
"hello"d.copy(buf);
|
|
assert(buf.toBytes() == "h\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o\x00\x00\x00");
|
|
}
|
|
}
|