phobos/std/outbuffer.d
Carsten Schlote f434dc3cae Add custom fill value to std.outbuffer.OutBuffer class
Extend the fill, alignSize, align{2,4} methods of `std.outbuffer.OutBuffer` to specify value to write
when filling (up to an alignment).

For flash device images it is desirable to use 0xff as the fill value,
because 0xff is the value of the unprogrammed flash memory cell. Padding
with 0 requires to programm the flash cell from 0xff to 0x00, which
increases wear and tear on the flash memory device. Usually there is some
larger block at the end if the flash memory image, which must be padded
up to the size of the flash device (usuallay a power of two). Instead
of padding with 0x00 the PR allows to fill with 0xff instead.

There might also be other use-cases, where it might be reasonable to fill
the alignment gaps with some other value than 0x00, e.g. when debugging
and viewing output data in a hex editor. It is easier to spot gaps, when
the padded spaces contain a custom value like 0x55 or 0xaa.

A new fill method was added, which allows filling with a user-defined value
instead of the 0 as in the previous implementation.
2022-04-29 12:57:10 +02:00

514 lines
13 KiB
D

// 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) @trusted 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, ...) @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...)(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.printf(" %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");
}
}