blob: 7e46d294f3d3d4dd807261dc95ff93d343a8d3cb [file] [log] [blame]
/**
* An expandable buffer in which you can write text or binary data.
*
* Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
* Authors: Walter Bright, https://www.digitalmars.com
* License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/root/outbuffer.d, root/_outbuffer.d)
* Documentation: https://dlang.org/phobos/dmd_root_outbuffer.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/root/outbuffer.d
*/
module dmd.common.outbuffer;
import core.stdc.stdarg;
import core.stdc.stdio;
import core.stdc.string;
import core.stdc.stdlib;
nothrow:
// In theory these functions should also restore errno, but we don't care because
// we abort application on error anyway.
extern (C) private pure @system @nogc nothrow
{
pragma(mangle, "malloc") void* pureMalloc(size_t);
pragma(mangle, "realloc") void* pureRealloc(void* ptr, size_t size);
pragma(mangle, "free") void pureFree(void* ptr);
}
debug
{
debug = stomp; // flush out dangling pointer problems by stomping on unused memory
}
/**
`OutBuffer` is a write-only output stream of untyped data. It is backed up by
a contiguous array or a memory-mapped file.
*/
struct OutBuffer
{
import dmd.common.file : FileMapping, touchFile, writeFile;
// IMPORTANT: PLEASE KEEP STATE AND DESTRUCTOR IN SYNC WITH DEFINITION IN ./outbuffer.h.
// state {
private ubyte[] data;
private size_t offset;
private bool notlinehead;
/// File mapping, if any. Use a pointer for ABI compatibility with the C++ counterpart.
/// If the pointer is non-null the store is a memory-mapped file, otherwise the store is RAM.
private FileMapping!ubyte* fileMapping;
/// Whether to indent
bool doindent;
/// Whether to indent by 4 spaces or by tabs;
bool spaces;
/// Current indent level
int level;
// state }
nothrow:
/**
Construct given size.
*/
this(size_t initialSize) nothrow
{
reserve(initialSize);
}
/**
Construct from filename. Will map the file into memory (or create it anew
if necessary) and start writing at the beginning of it.
Params:
filename = zero-terminated name of file to map into memory
*/
@trusted this(const(char)* filename)
{
FileMapping!ubyte model;
fileMapping = cast(FileMapping!ubyte*) malloc(model.sizeof);
memcpy(fileMapping, &model, model.sizeof);
fileMapping.__ctor(filename);
//fileMapping = new FileMapping!ubyte(filename);
data = (*fileMapping)[];
}
/**
Frees resources associated.
*/
extern (C++) void dtor() pure nothrow @trusted
{
if (fileMapping)
{
if (fileMapping.active)
fileMapping.close();
}
else
{
debug (stomp) memset(data.ptr, 0xFF, data.length);
pureFree(data.ptr);
}
}
/**
Frees resources associated automatically.
*/
extern (C++) ~this() pure nothrow @trusted
{
dtor();
}
/// For porting with ease from dmd.backend.outbuf.Outbuffer
ubyte* buf() nothrow {
return data.ptr;
}
/// For porting with ease from dmd.backend.outbuf.Outbuffer
ubyte** bufptr() nothrow {
static struct Array { size_t length; ubyte* ptr; }
auto a = cast(Array*) &data;
assert(a.length == data.length && a.ptr == data.ptr);
return &a.ptr;
}
extern (C++) size_t length() const pure @nogc @safe nothrow { return offset; }
/**********************
* Transfer ownership of the allocated data to the caller.
* Returns:
* pointer to the allocated data
*/
extern (C++) char* extractData() pure nothrow @nogc @trusted
{
char* p = cast(char*)data.ptr;
data = null;
offset = 0;
return p;
}
/**
Releases all resources associated with `this` and resets it as an empty
memory buffer. The config variables `notlinehead`, `doindent` etc. are
not changed.
*/
extern (C++) void destroy() pure nothrow @trusted
{
dtor();
fileMapping = null;
data = null;
offset = 0;
}
/**
Reserves `nbytes` bytes of additional memory (or file space) in advance.
The resulting capacity is at least the previous length plus `nbytes`.
Params:
nbytes = the number of additional bytes to reserve
*/
extern (C++) void reserve(size_t nbytes) pure nothrow
{
//debug (stomp) printf("OutBuffer::reserve: size = %lld, offset = %lld, nbytes = %lld\n", data.length, offset, nbytes);
const minSize = offset + nbytes;
if (data.length >= minSize)
return;
/* Increase by factor of 1.5; round up to 16 bytes.
* The odd formulation is so it will map onto single x86 LEA instruction.
*/
const size = ((minSize * 3 + 30) / 2) & ~15;
if (fileMapping && fileMapping.active)
{
fileMapping.resize(size);
data = (*fileMapping)[];
}
else
{
debug (stomp)
{
auto p = cast(ubyte*) pureMalloc(size);
p || assert(0, "OutBuffer: out of memory.");
memcpy(p, data.ptr, offset);
memset(data.ptr, 0xFF, data.length); // stomp old location
pureFree(data.ptr);
memset(p + offset, 0xff, size - offset); // stomp unused data
}
else
{
auto p = cast(ubyte*) pureRealloc(data.ptr, size);
p || assert(0, "OutBuffer: out of memory.");
memset(p + offset + nbytes, 0xff, size - offset - nbytes);
}
data = p[0 .. size];
}
}
/************************
* Shrink the size of the data to `size`.
* Params:
* size = new size of data, must be <= `.length`
*/
extern (C++) void setsize(size_t size) pure nothrow @nogc @safe
{
assert(size <= data.length);
offset = size;
}
extern (C++) void reset() pure nothrow @nogc @safe
{
offset = 0;
}
private void indent() pure nothrow
{
if (level)
{
const indentLevel = spaces ? level * 4 : level;
reserve(indentLevel);
data[offset .. offset + indentLevel] = (spaces ? ' ' : '\t');
offset += indentLevel;
}
notlinehead = true;
}
// Write an array to the buffer, no reserve check
@trusted nothrow
void writen(const void *b, size_t len)
{
memcpy(data.ptr + offset, b, len);
offset += len;
}
extern (C++) void write(const(void)* data, size_t nbytes) pure nothrow
{
write(data[0 .. nbytes]);
}
void write(const(void)[] buf) pure nothrow
{
if (doindent && !notlinehead)
indent();
reserve(buf.length);
memcpy(this.data.ptr + offset, buf.ptr, buf.length);
offset += buf.length;
}
/**
* Writes a 16 bit value, no reserve check.
*/
@trusted nothrow
void write16n(int v)
{
auto x = cast(ushort) v;
data[offset] = x & 0x00FF;
data[offset + 1] = x >> 8u;
offset += 2;
}
/**
* Writes a 16 bit value.
*/
void write16(int v) nothrow
{
auto u = cast(ushort) v;
write(&u, u.sizeof);
}
/**
* Writes a 32 bit int.
*/
void write32(int v) nothrow @trusted
{
write(&v, v.sizeof);
}
/**
* Writes a 64 bit int.
*/
@trusted void write64(long v) nothrow
{
write(&v, v.sizeof);
}
/// NOT zero-terminated
extern (C++) void writestring(const(char)* s) pure nothrow
{
if (!s)
return;
import core.stdc.string : strlen;
write(s[0 .. strlen(s)]);
}
/// ditto
void writestring(const(char)[] s) pure nothrow
{
write(s);
}
/// ditto
void writestring(string s) pure nothrow
{
write(s);
}
/// NOT zero-terminated, followed by newline
void writestringln(const(char)[] s) pure nothrow
{
writestring(s);
writenl();
}
// Zero-terminated
void writeString(const(char)* s) pure nothrow @trusted
{
write(s[0 .. strlen(s)+1]);
}
/// ditto
void writeString(const(char)[] s) pure nothrow
{
write(s);
writeByte(0);
}
/// ditto
void writeString(string s) pure nothrow
{
writeString(cast(const(char)[])(s));
}
extern (C++) void prependstring(const(char)* string) pure nothrow
{
size_t len = strlen(string);
reserve(len);
memmove(data.ptr + len, data.ptr, offset);
memcpy(data.ptr, string, len);
offset += len;
}
/// write newline
extern (C++) void writenl() pure nothrow
{
version (Windows)
{
writeword(0x0A0D); // newline is CR,LF on Microsoft OS's
}
else
{
writeByte('\n');
}
if (doindent)
notlinehead = false;
}
// Write n zeros; return pointer to start of zeros
@trusted
void *writezeros(size_t n) nothrow
{
reserve(n);
auto result = memset(data.ptr + offset, 0, n);
offset += n;
return result;
}
// Position buffer to accept the specified number of bytes at offset
@trusted
void position(size_t where, size_t nbytes) nothrow
{
if (where + nbytes > data.length)
{
reserve(where + nbytes - offset);
}
offset = where;
debug assert(offset + nbytes <= data.length);
}
/**
* Writes an 8 bit byte, no reserve check.
*/
extern (C++) @trusted nothrow
void writeByten(int b)
{
this.data[offset++] = cast(ubyte) b;
}
extern (C++) void writeByte(uint b) pure nothrow
{
if (doindent && !notlinehead && b != '\n')
indent();
reserve(1);
this.data[offset] = cast(ubyte)b;
offset++;
}
extern (C++) void writeUTF8(uint b) pure nothrow
{
reserve(6);
if (b <= 0x7F)
{
this.data[offset] = cast(ubyte)b;
offset++;
}
else if (b <= 0x7FF)
{
this.data[offset + 0] = cast(ubyte)((b >> 6) | 0xC0);
this.data[offset + 1] = cast(ubyte)((b & 0x3F) | 0x80);
offset += 2;
}
else if (b <= 0xFFFF)
{
this.data[offset + 0] = cast(ubyte)((b >> 12) | 0xE0);
this.data[offset + 1] = cast(ubyte)(((b >> 6) & 0x3F) | 0x80);
this.data[offset + 2] = cast(ubyte)((b & 0x3F) | 0x80);
offset += 3;
}
else if (b <= 0x1FFFFF)
{
this.data[offset + 0] = cast(ubyte)((b >> 18) | 0xF0);
this.data[offset + 1] = cast(ubyte)(((b >> 12) & 0x3F) | 0x80);
this.data[offset + 2] = cast(ubyte)(((b >> 6) & 0x3F) | 0x80);
this.data[offset + 3] = cast(ubyte)((b & 0x3F) | 0x80);
offset += 4;
}
else
assert(0);
}
extern (C++) void prependbyte(uint b) pure nothrow
{
reserve(1);
memmove(data.ptr + 1, data.ptr, offset);
data[0] = cast(ubyte)b;
offset++;
}
extern (C++) void writewchar(uint w) pure nothrow
{
version (Windows)
{
writeword(w);
}
else
{
write4(w);
}
}
extern (C++) void writeword(uint w) pure nothrow
{
version (Windows)
{
uint newline = 0x0A0D;
}
else
{
uint newline = '\n';
}
if (doindent && !notlinehead && w != newline)
indent();
reserve(2);
*cast(ushort*)(this.data.ptr + offset) = cast(ushort)w;
offset += 2;
}
extern (C++) void writeUTF16(uint w) pure nothrow
{
reserve(4);
if (w <= 0xFFFF)
{
*cast(ushort*)(this.data.ptr + offset) = cast(ushort)w;
offset += 2;
}
else if (w <= 0x10FFFF)
{
*cast(ushort*)(this.data.ptr + offset) = cast(ushort)((w >> 10) + 0xD7C0);
*cast(ushort*)(this.data.ptr + offset + 2) = cast(ushort)((w & 0x3FF) | 0xDC00);
offset += 4;
}
else
assert(0);
}
extern (C++) void write4(uint w) pure nothrow
{
version (Windows)
{
bool notnewline = w != 0x000A000D;
}
else
{
bool notnewline = true;
}
if (doindent && !notlinehead && notnewline)
indent();
reserve(4);
*cast(uint*)(this.data.ptr + offset) = w;
offset += 4;
}
extern (C++) void write(const OutBuffer* buf) pure nothrow
{
if (buf)
{
reserve(buf.offset);
memcpy(data.ptr + offset, buf.data.ptr, buf.offset);
offset += buf.offset;
}
}
extern (C++) void fill0(size_t nbytes) pure nothrow
{
reserve(nbytes);
memset(data.ptr + offset, 0, nbytes);
offset += nbytes;
}
/**
* Allocate space, but leave it uninitialized.
* Params:
* nbytes = amount to allocate
* Returns:
* slice of the allocated space to be filled in
*/
extern (D) char[] allocate(size_t nbytes) pure nothrow
{
reserve(nbytes);
offset += nbytes;
return cast(char[])data[offset - nbytes .. offset];
}
extern (C++) void vprintf(const(char)* format, va_list args) nothrow
{
int count;
if (doindent && !notlinehead)
indent();
uint psize = 128;
for (;;)
{
reserve(psize);
va_list va;
va_copy(va, args);
/*
The functions vprintf(), vfprintf(), vsprintf(), vsnprintf()
are equivalent to the functions printf(), fprintf(), sprintf(),
snprintf(), respectively, except that they are called with a
va_list instead of a variable number of arguments. These
functions do not call the va_end macro. Consequently, the value
of ap is undefined after the call. The application should call
va_end(ap) itself afterwards.
*/
count = vsnprintf(cast(char*)data.ptr + offset, psize, format, va);
va_end(va);
if (count == -1) // snn.lib and older libcmt.lib return -1 if buffer too small
psize *= 2;
else if (count >= psize)
psize = count + 1;
else
break;
}
offset += count;
// if (mem.isGCEnabled)
memset(data.ptr + offset, 0xff, psize - count);
}
static if (__VERSION__ < 2092)
{
extern (C++) void printf(const(char)* format, ...) nothrow
{
va_list ap;
va_start(ap, format);
vprintf(format, ap);
va_end(ap);
}
}
else
{
pragma(printf) extern (C++) void printf(const(char)* format, ...) nothrow
{
va_list ap;
va_start(ap, format);
vprintf(format, ap);
va_end(ap);
}
}
/**************************************
* Convert `u` to a string and append it to the buffer.
* Params:
* u = integral value to append
*/
extern (C++) void print(ulong u) pure nothrow
{
UnsignedStringBuf buf = void;
writestring(unsignedToTempString(u, buf));
}
extern (C++) void bracket(char left, char right) pure nothrow
{
reserve(2);
memmove(data.ptr + 1, data.ptr, offset);
data[0] = left;
data[offset + 1] = right;
offset += 2;
}
/******************
* Insert left at i, and right at j.
* Return index just past right.
*/
extern (C++) size_t bracket(size_t i, const(char)* left, size_t j, const(char)* right) pure nothrow
{
size_t leftlen = strlen(left);
size_t rightlen = strlen(right);
reserve(leftlen + rightlen);
insert(i, left, leftlen);
insert(j + leftlen, right, rightlen);
return j + leftlen + rightlen;
}
extern (C++) void spread(size_t offset, size_t nbytes) pure nothrow
{
reserve(nbytes);
memmove(data.ptr + offset + nbytes, data.ptr + offset, this.offset - offset);
this.offset += nbytes;
}
/****************************************
* Returns: offset + nbytes
*/
extern (C++) size_t insert(size_t offset, const(void)* p, size_t nbytes) pure nothrow
{
spread(offset, nbytes);
memmove(data.ptr + offset, p, nbytes);
return offset + nbytes;
}
size_t insert(size_t offset, const(char)[] s) pure nothrow
{
return insert(offset, s.ptr, s.length);
}
extern (C++) void remove(size_t offset, size_t nbytes) pure nothrow @nogc
{
memmove(data.ptr + offset, data.ptr + offset + nbytes, this.offset - (offset + nbytes));
this.offset -= nbytes;
}
/**
* Returns:
* a non-owning const slice of the buffer contents
*/
extern (D) const(char)[] opSlice() const pure nothrow @nogc @safe
{
return cast(const(char)[])data[0 .. offset];
}
extern (D) const(char)[] opSlice(size_t lwr, size_t upr) const pure nothrow @nogc @safe
{
return cast(const(char)[])data[lwr .. upr];
}
extern (D) char opIndex(size_t i) const pure nothrow @nogc @safe
{
return cast(char)data[i];
}
alias opDollar = length;
/***********************************
* Extract the data as a slice and take ownership of it.
*
* When `true` is passed as an argument, this function behaves
* like `dmd.utils.toDString(thisbuffer.extractChars())`.
*
* Params:
* nullTerminate = When `true`, the data will be `null` terminated.
* This is useful to call C functions or store
* the result in `Strings`. Defaults to `false`.
*/
extern (D) char[] extractSlice(bool nullTerminate = false) pure nothrow
{
const length = offset;
if (!nullTerminate)
return extractData()[0 .. length];
// There's already a terminating `'\0'`
if (length && data[length - 1] == '\0')
return extractData()[0 .. length - 1];
writeByte(0);
return extractData()[0 .. length];
}
extern (D) byte[] extractUbyteSlice(bool nullTerminate = false) pure nothrow
{
return cast(byte[]) extractSlice(nullTerminate);
}
// Append terminating null if necessary and get view of internal buffer
extern (C++) char* peekChars() pure nothrow
{
if (!offset || data[offset - 1] != '\0')
{
writeByte(0);
offset--; // allow appending more
}
return cast(char*)data.ptr;
}
// Append terminating null if necessary and take ownership of data
extern (C++) char* extractChars() pure nothrow
{
if (!offset || data[offset - 1] != '\0')
writeByte(0);
return extractData();
}
void writesLEB128(int value) pure nothrow
{
while (1)
{
ubyte b = value & 0x7F;
value >>= 7; // arithmetic right shift
if ((value == 0 && !(b & 0x40)) ||
(value == -1 && (b & 0x40)))
{
writeByte(b);
break;
}
writeByte(b | 0x80);
}
}
void writeuLEB128(uint value) pure nothrow
{
do
{
ubyte b = value & 0x7F;
value >>= 7;
if (value)
b |= 0x80;
writeByte(b);
} while (value);
}
/**
Destructively saves the contents of `this` to `filename`. As an
optimization, if the file already has identical contents with the buffer,
no copying is done. This is because on SSD drives reading is often much
faster than writing and because there's a high likelihood an identical
file is written during the build process.
Params:
filename = the name of the file to receive the contents
Returns: `true` iff the operation succeeded.
*/
extern(D) bool moveToFile(const char* filename)
{
bool result = true;
const bool identical = this[] == FileMapping!(const ubyte)(filename)[];
if (fileMapping && fileMapping.active)
{
// Defer to corresponding functions in FileMapping.
if (identical)
{
result = fileMapping.discard();
}
else
{
// Resize to fit to get rid of the slack bytes at the end
fileMapping.resize(offset);
result = fileMapping.moveToFile(filename);
}
// Can't call destroy() here because the file mapping is already closed.
data = null;
offset = 0;
}
else
{
if (!identical)
writeFile(filename, this[]);
destroy();
}
return identical
? result && touchFile(filename)
: result;
}
}
/****** copied from core.internal.string *************/
private:
alias UnsignedStringBuf = char[20];
char[] unsignedToTempString(ulong value, char[] buf, uint radix = 10) @safe pure nothrow @nogc
{
size_t i = buf.length;
do
{
if (value < radix)
{
ubyte x = cast(ubyte)value;
buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a');
break;
}
else
{
ubyte x = cast(ubyte)(value % radix);
value /= radix;
buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a');
}
} while (value);
return buf[i .. $];
}
/************* unit tests **************************************************/
unittest
{
OutBuffer buf;
buf.printf("betty");
buf.insert(1, "xx".ptr, 2);
buf.insert(3, "yy");
buf.remove(4, 1);
buf.bracket('(', ')');
const char[] s = buf[];
assert(s == "(bxxyetty)");
buf.destroy();
}
unittest
{
OutBuffer buf;
buf.writestring("abc".ptr);
buf.prependstring("def");
buf.prependbyte('x');
OutBuffer buf2;
buf2.writestring("mmm");
buf.write(&buf2);
char[] s = buf.extractSlice();
assert(s == "xdefabcmmm");
}
unittest
{
OutBuffer buf;
buf.writeByte('a');
char[] s = buf.extractSlice();
assert(s == "a");
buf.writeByte('b');
char[] t = buf.extractSlice();
assert(t == "b");
}
unittest
{
OutBuffer buf;
char* p = buf.peekChars();
assert(*p == 0);
buf.writeByte('s');
char* q = buf.peekChars();
assert(strcmp(q, "s") == 0);
}
unittest
{
char[10] buf;
char[] s = unsignedToTempString(278, buf[], 10);
assert(s == "278");
s = unsignedToTempString(1, buf[], 10);
assert(s == "1");
s = unsignedToTempString(8, buf[], 2);
assert(s == "1000");
s = unsignedToTempString(29, buf[], 16);
assert(s == "1d");
}
unittest
{
OutBuffer buf;
buf.writeUTF8(0x0000_0011);
buf.writeUTF8(0x0000_0111);
buf.writeUTF8(0x0000_1111);
buf.writeUTF8(0x0001_1111);
buf.writeUTF8(0x0010_0000);
assert(buf[] == "\x11\U00000111\U00001111\U00011111\U00100000");
buf.reset();
buf.writeUTF16(0x0000_0011);
buf.writeUTF16(0x0010_FFFF);
assert(buf[] == cast(string) "\u0011\U0010FFFF"w);
}
unittest
{
OutBuffer buf;
buf.doindent = true;
const(char)[] s = "abc";
buf.writestring(s);
buf.level += 1;
buf.indent();
buf.writestring("abs");
assert(buf[] == "abc\tabs");
buf.setsize(4);
assert(buf.length == 4);
}