Newer
Older
alicedbg / src / adbg / utils / strings.d
/// String helper functions. Simple string functions to aid redundant typing.
///
/// Authors: dd86k <dd@dax.moe>
/// Copyright: © dd86k <dd@dax.moe>
/// License: BSD-3-Clause-Clear
module adbg.utils.strings;

import adbg.include.c.stdio;
import core.stdc.stdarg;
import core.stdc.string;

extern (C):

/// An empty string in case compilers does not support pool strings.
__gshared char *empty_string = cast(char*)"";

/// Gets the next line out of a file stream.
/// This skips empty lines.
/// The extracted line is null-terminated.
/// Params:
///   bf = Line buffer input.
///   bfsz = Line buffer input size.
///   lnsz = Line length reference.
///   file = File handle.
/// Returns: Line length.
size_t adbg_util_getlinef(char *bf, size_t bfsz, size_t *lnsz, FILE *file) {
	if (bf == null || bfsz == 0 || lnsz == null || file == null)
		return 0;
	
	import core.stdc.ctype : isprint;
	
	size_t i; /// Line buffer index
	
	// If fgetc return EOF, it is non-printable
	for ( ; i < bfsz ; ++i) {
		int c = fgetc(file);
		
		//TODO: Include tab as accepted character
		if (isprint(c) == false)
			break;
		
		bf[i] = cast(char)c;
	}
	
	bf[i] = 0;
	*lnsz = i;
	return i;
}

unittest {
	import std.stdio : writefln;
	import std.file : write, tempDir, remove;
	import std.path : buildPath;
	import std.string : toStringz;
	
	string tmppath = buildPath(tempDir, "alicedbg_unittest");
	write(tmppath, "123\n\nabc");
	FILE *fd = fopen(tmppath.toStringz, "r");
	
	char[16] line = void;
	size_t linesz = void;
	size_t i;
	while (adbg_util_getlinef(line.ptr, 16, &linesz, fd)) {
		final switch (++i) {
		case 1: assert(strncmp(line.ptr, "123", linesz) == 0); break;
		case 2: assert(strncmp(line.ptr, "abc", linesz) == 0); break;
		}
	}
	
	fclose(fd);
	remove(tmppath);
}

/// Gets the next line out of a file stream.
/// This skips empty lines.
/// The extracted line is null-terminated.
/// Params:
///   bf = Line buffer input.
///   bfsz = Line buffer input size.
///   lnsz = Line length reference.
///   src = Null-terminated buffer source.
///   srcidx = Index reminder. It's best advised you don't touch this variable between calls.
/// Returns: Line length.
size_t adbg_util_getline(char *bf, size_t bfsz, size_t *lnsz, const(char) *src, size_t *srcidx) {
	if (bf == null || bfsz == 0 || lnsz == null || src == null || srcidx == null)
		return 0;
	
	import core.stdc.ctype : isprint;
	
	size_t i; /// Line buffer index
	size_t s = *srcidx; /// Source buffer index
	
	// checking 0 in for-loop is important because somehow isprint might let it pass?
	for (; src[s] && i < bfsz; ++i) {
		int c = src[s++]; // unconditionally prep next pos
		
		//TODO: Include tab as accepted character
		if (isprint(c) == false)
			break;
		
		bf[i] = cast(char)c;
	}
	
	bf[i] = 0;
	*srcidx = s;
	*lnsz = i;
	return i;
}

unittest {
	const(char) *src = "123\n\nabc";
	char[16] line = void;
	size_t linesz = void;
	size_t idx;
	size_t i;
	while (adbg_util_getline(line.ptr, 16, &linesz, src, &idx)) {
		final switch (++i) {
		case 1: assert(line[0..linesz] == "123"); break;
		case 2: assert(line[0..linesz] == "abc"); break;
		}
	}
}

//TODO: adbg_util_getline2: version without copying line to buffer, but modifies it

/// Convert a hex string into a byte array.
/// Params:
/// 	dst = Destination buffer.
/// 	sz = Destination buffer capacity in bytes.
/// 	src = Source string buffer.
/// 	newsz = New destination size in bytes.
/// Returns: Error code if non-zero.
int adbg_util_hex_array(ubyte *dst, size_t sz, const(char) *src, ref size_t newsz) {
	bool upper = true;
	ubyte b = void, bh = void;
	size_t di, si;
	for (; di < sz; ++si) {
		char c = src[si];
		if (c == 0) break;
		
		if (c >= '0' && c <= '9') {
			b = cast(ubyte)(c - '0');
		} else if (c >= 'a' && c <= 'f') {
			b = cast(ubyte)(c - 87);
		} else if (c >= 'A' && c <= 'F') {
			b = cast(ubyte)(c - 55);
		} else continue;
		
		if (upper) {
			bh = cast(ubyte)(b << 4);
		} else {
			b |= bh;
			dst[di++] = b;
		}
		upper = !upper;
	}
	newsz = di;
	return di >= sz;
}

/// adbg_util_hex_array
@system unittest {
	ubyte[8] buf = void;
	size_t sz = void;
	assert(adbg_util_hex_array(buf.ptr, 8, "12AAcc", sz) == 0);
	assert(sz == 3);
	assert(buf[0] == 0x12);
	assert(buf[1] == 0xaa);
	assert(buf[2] == 0xcc);
}

deprecated("Use safer adbg_strings_flatten")
size_t adbg_util_argv_flatten(char *buf, int buflen, const(char) **argv) {
	if (argv == null)
		return 0;

	ptrdiff_t ai, bi, t;
	while (argv[ai]) {
		t = snprintf(buf + bi, buflen, "%s ", argv[ai]);
		if (t < 0)
			return 0;
		bi += t;
		++ai;
	}

	return bi;
}

/// Flatten a multi-vector string into a singular buffer. Like an Array.join() function.
/// Params:
/// 	buf = Destination buffer.
/// 	buflen = Destination buffer size.
/// 	argc = Number of input arguments.
/// 	argv = Argument vector.
/// 	spaces = Number of spaces in-between items.
/// Returns: Number of characters written, excluding null.
int adbg_strings_flatten(char *buf, int buflen, int argc, const(char) **argv, int spaces) {
	if (argv == null)
		return 0;

	int bufidx;
	for (int i; i < argc; ++i) {
		const(char) *arg = argv[i];
		int len = cast(int)strlen(arg);
		
		// Copy rest
		if (bufidx + len >= buflen) {
			size_t rem = buflen - bufidx;
			if (rem == 0) return bufidx;
			
			memcpy(buf + bufidx, arg, rem);
			bufidx += rem - 1;
			buf[bufidx] = 0;
			return bufidx;
		}
		
		// Copy buffer
		memcpy(buf + bufidx, arg, len);
		bufidx += len;
		
		if (i + 1 >= argc) continue;
		if (bufidx + spaces >= buflen)
			spaces = buflen - bufidx;
		
		memset(buf + bufidx, ' ', spaces);
		bufidx += spaces;
	}

	buf[bufidx] = 0;
	return bufidx;
}
unittest {
	static int argc = 3;
	static const(char)** argv = ["one", "two", "three"];
	
	// Buffer OK
	enum B1LEN = 128;
	char[B1LEN] b1 = void;
	int r1 = adbg_strings_flatten(b1.ptr, B1LEN, argc, argv, 1);
	assert(r1 == 13); // Chars written
	assert(b1[0..r1] == "one two three"); // Chars written
	
	// Buffer too small
	enum B2LEN = 10;
	char[B2LEN] b2 = void;
	int r2 = adbg_strings_flatten(b2.ptr, B2LEN, argc, argv, 1);
	assert(r2 == B2LEN - 1); // Chars written, excluding null
	assert(b2[0..r2] == "one two t"); // Chars written
}

// NOTE: Kind would have preferred returning as int (argc)...
/// Expand a command-line string into an array of items.
/// The input string is copied into the internal buffer.
/// Params:
/// 	str = Input string
/// 	argc = Pointer that will receive the argument count, can be null
/// Returns:
/// 	Internal buffer with seperated items, otherwise null if no items were
/// 	processed.
/// Note: The internal buffer is 2048 characters and processes up to 32 items.
char** adbg_util_expand(const(char) *str, int *argc) {
	enum BUFFER_LEN   = 2048;
	enum BUFFER_ITEMS = 32;
	__gshared char[BUFFER_LEN] _buffer;	/// internal string buffer
	__gshared char*[BUFFER_ITEMS] _argv;	/// internal argv buffer
	
	if (str == null)
		return null;

	strncpy(_buffer.ptr, str, BUFFER_LEN);
	
	size_t index;	/// string character index
	int _argc;	/// argument counter
	
L_ARG:
	// maximum number of items reached
	if (_argc >= BUFFER_ITEMS)
		goto L_RETURN;
	
	// move pointer to first non-white character
	A: while (index < BUFFER_LEN) {
		const char c = _buffer[index];
		
		switch (c) {
		case 0: goto L_RETURN;
		case '\n', '\r':
			_buffer[index] = 0;
			goto L_RETURN;
		case ' ', '\t':
			++index;
			continue;
		default: break A;
		}
	}
	
	// set argument at position
	_argv[_argc++] = cast(char*)_buffer + index;
	
	// get how long the parameter length is
	while (index < BUFFER_LEN) {
		const char c = _buffer[index];
		
		switch (c) {
		case 0: goto L_RETURN;
		case'\n', '\r':
			_buffer[index] = 0;
			goto L_RETURN;
		case ' ', '\t':
			_buffer[index++] = 0;
			goto L_ARG;
		default: ++index; continue;
		}
	}
	
	// reached the end before we knew it
L_RETURN:
	_argv[_argc] = null;
	if (argc) *argc = _argc;
	return _argc ? cast(char**)_argv : null;
}

unittest {
	struct Test {
		string input;
		const(char)*[] output;
	}
	immutable Test[] tests = [
		{ null, [] },
		{ "", [] },
		{ "test", [ "test" ] },
		{ "command test", [ "command", "test" ] },
		{ "readline\n", [ "readline" ] },
		{ "readline param\n", [ "readline", "param" ] },
		{ "decent day, isn't it?", [ "decent", "day,", "isn't", "it?" ] },
		{ "1 2 3 4 5 6 7 8 9", [ "1", "2", "3", "4", "5", "6", "7", "8", "9" ] },
		{ "abc\ndef", [ "abc", "def" ] },
	];
	
	foreach (test; tests) {
		int argc = void;
		char** argv = adbg_util_expand(test.input.ptr, &argc);
		
		// Part of user code
		if (argv == null) continue;
		
		for (uint i; i < argc; ++i) {
			assert(strcmp(argv[i], cast(char*)test.output[i]) == 0, test.input);
		}
	}
}

/// Process a comma-seperated list of key=value pairs into an internal buffer.
/// Params:
/// 	str = String buffer
/// Returns:
/// 	Internal buffer with seperated items, otherwise null if no items were
/// 	processed.
/// Note: The internal buffer is 2048 characters and processes up to 32 items.
char** adbg_util_env(const(char) *str) {
	enum BUFFER_LEN   = 2048;
	enum BUFFER_ITEMS = 32;
	__gshared char[BUFFER_LEN] _buffer;	/// internal string buffer
	__gshared char*[BUFFER_ITEMS] _envp;	/// internal envp buffer
	
	char *last = cast(char*)_buffer; /// last item position
	
	strncpy(last, str, BUFFER_LEN);
	
	_envp[0] = last;
	
	size_t bindex, eindex; // buffer and env indexes
	
	while (bindex < BUFFER_LEN) {
		char c = _buffer[bindex];
		
		switch (c) {
		case 0: goto L_RETURN;
		case ',':
			_buffer[bindex++] = 0;
			last = cast(char*)_buffer + bindex;
			_envp[++eindex] = last;
			continue;
		default:
		}
		
		++bindex;
	}
	
L_RETURN:
	return eindex ? cast(char**)_envp : null;
}

/// Move (copy) the pointers of two arrays.
/// Params:
/// 	dst = Destination pointer
/// 	dstsz = Destination buffer size, typically how many items it can hold
/// 	src = Source pointer
/// 	srcsz = Source buffer size, typically how many items to transfer
/// Returns:
/// 	Number of items copied
int adbg_util_move(void **dst, int dstsz, void **src, int srcsz) {
	int r;
	
	while (r < dstsz && r < srcsz) {
		dst[r] = src[r];
		++r;
	}
	
	return r;
}

//
// Fast hexadecimal formatting
//

/// Hexadecimal map for strx0* functions to provide much faster %x parsing
private immutable char [16]hexMapLower = "0123456789abcdef";
/// Hexadecimal map for strx0* functions to provide much faster %X parsing
private immutable char [16]hexMapUpper = "0123456789ABCDEF";

/**
 * Quick and dirty conversion function to convert an ubyte value to a
 * '0'-padded hexadecimal string. Faster than using vsnprintf.
 * Params:
 * 	v = 8-bit value
 * 	upper = Use upper hexadecimal character
 * Returns: Null-terminated hexadecimal string
 */
const(char) *adbg_util_strx02(ubyte v, bool upper = false) {
	__gshared char [3]b = void;

	const(char) *h = cast(char*)(upper ? hexMapUpper : hexMapLower);

	b[0] = h[v >> 4];
	b[1] = h[v & 0xF];
	b[2] = 0;

	return cast(char*)b;
}

/**
 * Quick and dirty conversion function to convert an ushort value to a
 * '0'-padded hexadecimal string. Faster than using vsnprintf.
 * Params:
 * 	v = 8-bit value
 * 	upper = Use upper hexadecimal character
 * Returns: Null-terminated hexadecimal string
 */
const(char) *adbg_util_strx04(ushort v, bool upper = false) {
	__gshared char [5]b = void;

	const(char) *h = cast(char*)(upper ? hexMapUpper : hexMapLower);

	b[4] = 0;
	b[3] = h[v & 0xF];
	b[2] = h[(v >>= 4) & 0xF];
	b[1] = h[(v >>= 4) & 0xF];
	b[0] = h[(v >>= 4) & 0xF];

	return cast(char*)b;
}

/**
 * Quick and dirty conversion function to convert an uint value to a
 * '0'-padded hexadecimal string. Faster than using vsnprintf.
 * Params:
 * 	v = 8-bit value
 * 	upper = Use upper hexadecimal character
 * Returns: Null-terminated hexadecimal string
 */
const(char) *adbg_util_strx08(uint v, bool upper = false) {
	__gshared char [9]b = void;

	const(char) *h = cast(char*)(upper ? hexMapUpper : hexMapLower);

	b[8] = 0;
	b[7] = h[v & 0xF];
	b[6] = h[(v >>= 4) & 0xF];
	b[5] = h[(v >>= 4) & 0xF];
	b[4] = h[(v >>= 4) & 0xF];
	b[3] = h[(v >>= 4) & 0xF];
	b[2] = h[(v >>= 4) & 0xF];
	b[1] = h[(v >>= 4) & 0xF];
	b[0] = h[(v >>= 4) & 0xF];

	return cast(char*)b;
}

/**
 * Quick and dirty conversion function to convert an ulong value to a
 * '0'-padded hexadecimal string. Faster than using vsnprintf.
 * Params:
 * 	v = 8-bit value
 * 	upper = Use upper hexadecimal character
 * Returns: Null-terminated hexadecimal string
 */
const(char) *adbg_util_strx016(ulong v, bool upper = false) {
	__gshared char [17]b = void;

	const(char) *h = cast(char*)(upper ? hexMapUpper : hexMapLower);

	b[16] = 0;
	b[15] = h[v & 0xF];
	b[14] = h[(v >>= 4) & 0xF];
	b[13] = h[(v >>= 4) & 0xF];
	b[12] = h[(v >>= 4) & 0xF];
	b[11] = h[(v >>= 4) & 0xF];
	b[10] = h[(v >>= 4) & 0xF];
	b[9]  = h[(v >>= 4) & 0xF];
	b[8]  = h[(v >>= 4) & 0xF];
	b[7]  = h[(v >>= 4) & 0xF];
	b[6]  = h[(v >>= 4) & 0xF];
	b[5]  = h[(v >>= 4) & 0xF];
	b[4]  = h[(v >>= 4) & 0xF];
	b[3]  = h[(v >>= 4) & 0xF];
	b[2]  = h[(v >>= 4) & 0xF];
	b[1]  = h[(v >>= 4) & 0xF];
	b[0]  = h[(v >>= 4) & 0xF];

	return cast(char*)b;
}

//
// Generic string manipulation
//

/**
 * Lower case string buffer ('A' to 'Z' only).
 * Params:
 * 	buf  = String buffer
 * 	size = Buffer size
 */
void adbg_util_str_lowercase(char *buf, size_t size) {
	for (size_t i; buf[i] && i < size; ++i)
		if (buf[i] >= 'A' && buf[i] <= 'Z')
			buf[i] += 32;
}

// NOTE: adbg_string_t is currently used for disassembler v1
//       It might be kept or removed in future versions

/// Internal structure used to append an existing buffer new typed elements.
/// 
/// Should look more like MFC's CString.
//TODO: Add decimal
//      addu8/addu16/addu32/addu64(T v, bool signed)
//TODO: Consider having settings instead of arguments
//      bool pad    = false;
//      bool signed = false;
//TODO: bool positive parameter
//      Adds '+' if >=0
//TODO: Better constructors (for dynamic and constant strings)
struct adbg_string_t {
	char  *str;	/// String pointer
	size_t size;	/// Buffer capacity
	size_t length;	/// Position, count
	
	/// Inits a string position tracker with a buffer and its size.
	/// This does not create a string.
	/// Params:
	/// 	buffer = Buffer pointer.
	/// 	buffersz = Buffer capacity.
	this(char *buffer, size_t buffersz) {
		str  = buffer;
		size = buffersz;
		length  = 0;
	}
	/// Reset counters and optionally zero-fill the buffer.
	/// Params: zero = If true, fills the buffer of zeros.
	void reset(bool zero = false) {
		str[0] = 0;
		if (zero)
			for (size_t p = 1; p < size; ++p)
				str[p] = 0;
		length = 0;
	}
	/// Add character to buffer.
	/// Params: c = Character
	/// Returns: True if buffer exhausted.
	bool addc(char c) {
		if (length >= size)
			return true;
		char *s = str + length++;
		*s = c;
		*(s + 1) = 0;
		return false;
	}
	/// Add a constant string to buffer.
	/// Params: s = String
	/// Returns: True if buffer exhausted.
	bool adds(const(char) *s) {
		size_t sz = size - 1;
		if (s == null)
			goto L_RET;
		for (size_t si; length < sz && s[si]; ++length, ++si)
			str[length] = s[si];
		str[length] = 0;
	L_RET:
		return length >= sz;
	}
	/// Add multiple items to buffer.
	/// Params:
	/// 	fmt = Format specifier.
	/// 	... = Parameters.
	/// Returns: True if buffer exhausted.
	bool addf(const(char) *fmt, ...) {
		va_list va = void;
		va_start(va, fmt);
		return addv(fmt, va);
	}
	/// Add a list of items to buffer.
	/// Params:
	/// 	fmt = Format specifier.
	/// 	va = va_list object.
	/// Returns: True if buffer exhausted.
	bool addv(const(char) *fmt, va_list va) {
		length += vsnprintf(str + length, size - length, fmt,va);
		return length >= size;
	}
	/// Add a hexadecimal byte to buffer.
	/// Params:
	/// 	v = ubyte value.
	/// 	pad = If set, pads with zero.
	/// Returns: True if buffer exhausted.
	bool addx8(ubyte v, bool pad = false) {
		if (length + 4 >= size) return true;
		ubyte vh = v >> 4;
		ubyte vl = v & 15;
		if (vh || pad) str[length++] = hexMapLower[vh];
		str[length++] = hexMapLower[vl];
		str[length] = 0;
		return length >= size;
	}
	/// Add a hexadecimal 16-bit value to buffer.
	/// Params:
	/// 	v = ushort value.
	/// 	pad = If set, pads with zero.
	/// Returns: True if buffer exhausted.
	bool addx16(ushort v, bool pad = false) {
		for (int shift = 12; length < size && shift >= 0; shift -= 4) {
			ushort h = (v >> shift) & 15;
			if (h == 0 && pad == false && shift > 0) continue;
			str[length++] = hexMapLower[h];
			if (h) pad = true;
		}
		str[length] = 0;
		return length >= size;
	}
	/// Add a hexadecimal 32-bit value to buffer.
	/// Params:
	/// 	v = uint value.
	/// 	pad = If set, pads with zero.
	/// Returns: True if buffer exhausted.
	bool addx32(uint v, bool pad = false) {
		for (int shift = 28; length < size && shift >= 0; shift -= 4) {
			uint h = (v >> shift) & 15;
			if (h == 0 && pad == false && shift > 0) continue;
			str[length++] = hexMapLower[h];
			if (h) pad = true;
		}
		str[length] = 0;
		return length >= size;
	}
	/// Add a hexadecimal 64-bit value to buffer.
	/// Params:
	/// 	v = ulong value.
	/// 	pad = If set, pads with zero.
	/// Returns: True if buffer exhausted.
	bool addx64(ulong v, bool pad = false) {
		for (int shift = 60; length < size && shift >= 0; shift -= 4) {
			ulong h = (v >> shift) & 15;
			if (h == 0 && pad == false && shift > 0) continue;
			version (D_LP64)
				str[length++] = hexMapLower[h];
			else // for 32-bit systems
				str[length++] = hexMapLower[cast(uint)h];
			if (h) pad = true;
		}
		str[length] = 0;
		return length >= size;
	}
}

unittest {
	import adbg.utils.strings : adbg_string_t;
	
	enum BUFFER_SIZE = 80;
	enum LAST_ITEM = BUFFER_SIZE - 1;
	
	char[BUFFER_SIZE] buffer = void;
	
	// init
	
	adbg_string_t s = adbg_string_t(buffer.ptr, BUFFER_SIZE);
	assert(s.size == BUFFER_SIZE);
	assert(s.length  == 0);
	assert(s.str  == &buffer[0]);
	
	// reset
	
	s.length = 3;
	s.reset(true);
	assert(buffer[0] == 0);
	assert(buffer[1] == 0);
	assert(buffer[LAST_ITEM] == 0);
	assert(s.length == 0);
	
	// add(char)
	
	s.reset();
	assert(s.addc('a') == false);
	assert(buffer[0] == 'a');
	assert(buffer[1] == 0);
	
	s.reset();
	assert(s.addc('a') == false);
	assert(s.addc('b') == false);
	assert(s.addc('c') == false);
	assert(strcmp(s.str, "abc") == 0);
	
	// add(string)
	
	s.reset();
	assert(s.adds("hello") == false);
	assert(buffer[0] == 'h');
	assert(buffer[1] == 'e');
	assert(buffer[2] == 'l');
	assert(buffer[3] == 'l');
	assert(buffer[4] == 'o');
	assert(buffer[5] == 0);
	assert(buffer[6] == 0);
	assert(buffer[7] == 0);
	assert(buffer[8] == 0);
	
	s.reset();
	immutable string lorem =
		`Lorem ipsum dolor sit amet, consectetur adipiscing elit. `~
		`Etiam dignissim iaculis lectus. Aliquam volutpat rhoncus dignissim. `~
		`Donec maximus diam eros, a euismod quam consectetur sit amet. `~
		`Morbi vel ante viverra, condimentum elit porttitor, tempus metus. `~
		`Cras eget interdum turpis, vitae egestas ipsum. `~
		`Nam accumsan aliquam enim, id sodales tellus hendrerit id. `~
		`Proin vulputate hendrerit accumsan. Etiam vitae tempor libero.`;
	assert(lorem.length > s.size);
	assert(s.adds(lorem.ptr));
	assert(buffer[0] == 'L');
	
	s.reset(true);
	s.adds("123");
	s.adds("abc");
	assert(strcmp(s.str, "123abc") == 0);
	
	// addx8
	
	s.reset();
	assert(s.addx8(0xe0) == false);
	assert(buffer[0] == 'e');
	assert(buffer[1] == '0');
	assert(buffer[2] == 0);
	
	s.reset();
	assert(s.addx8(0) == false);
	assert(buffer[0] == '0');
	assert(buffer[1] == 0);
	
	s.reset();
	assert(s.addx8(0, true) == false);
	assert(buffer[0] == '0');
	assert(buffer[1] == '0');
	assert(buffer[2] == 0);
	
	s.reset();
	assert(s.addx8(0xe) == false);
	assert(buffer[0]  == 'e');
	assert(buffer[1] == 0);
	
	s.reset();
	assert(s.addx8(0xe, true) == false);
	assert(buffer[0] == '0');
	assert(buffer[1] == 'e');
	assert(buffer[2] == 0);
	
	s.reset();
	assert(s.addx8(0x80) == false);
	assert(s.addx8(0x86) == false);
	assert(buffer[0] == '8');
	assert(buffer[1] == '0');
	assert(buffer[2] == '8');
	assert(buffer[3] == '6');
	assert(buffer[4] == 0);
	
	// addx16
	
	s.reset();
	assert(s.addx16(0xabcd) == false);
	assert(buffer[0] == 'a');
	assert(buffer[1] == 'b');
	assert(buffer[2] == 'c');
	assert(buffer[3] == 'd');
	assert(buffer[4] == 0);
	
	s.reset();
	assert(s.addx16(0xff) == false);
	assert(buffer[0] == 'f');
	assert(buffer[1] == 'f');
	assert(buffer[2] == 0);
	
	s.reset();
	assert(s.addx16(0xee, true) == false);
	assert(buffer[0] == '0');
	assert(buffer[1] == '0');
	assert(buffer[2] == 'e');
	assert(buffer[3] == 'e');
	assert(buffer[4] == 0);
	
	s.reset();
	assert(s.addx16(0) == false);
	assert(buffer[0] == '0');
	assert(buffer[1] == 0);
	
	s.reset();
	assert(s.addx16(0, true) == false);
	assert(buffer[0] == '0');
	assert(buffer[1] == '0');
	assert(buffer[2] == '0');
	assert(buffer[3] == '0');
	assert(buffer[4] == 0);
	
	s.reset();
	assert(s.addx16(0x8086) == false);
	assert(buffer[0]  == '8');
	assert(buffer[1]  == '0');
	assert(buffer[2]  == '8');
	assert(buffer[3]  == '6');
	assert(buffer[4] == 0);
	
	s.reset();
	assert(s.addx16(0x80) == false);
	assert(s.addx16(0x86) == false);
	assert(buffer[0]  == '8');
	assert(buffer[1]  == '0');
	assert(buffer[2]  == '8');
	assert(buffer[3]  == '6');
	assert(buffer[4] == 0);
	
	// addx32
	
	s.reset();
	assert(s.addx32(0x1234_abcd) == false);
	assert(buffer[0]  == '1');
	assert(buffer[1]  == '2');
	assert(buffer[2]  == '3');
	assert(buffer[3]  == '4');
	assert(buffer[4]  == 'a');
	assert(buffer[5]  == 'b');
	assert(buffer[6]  == 'c');
	assert(buffer[7]  == 'd');
	assert(buffer[8] == 0);
	
	s.reset();
	assert(s.addx32(0xed) == false);
	assert(buffer[0]  == 'e');
	assert(buffer[1]  == 'd');
	assert(buffer[2] == 0);
	
	s.reset();
	assert(s.addx32(0xcc, true) == false);
	assert(buffer[0]  == '0');
	assert(buffer[1]  == '0');
	assert(buffer[2]  == '0');
	assert(buffer[3]  == '0');
	assert(buffer[4]  == '0');
	assert(buffer[5]  == '0');
	assert(buffer[6]  == 'c');
	assert(buffer[7]  == 'c');
	assert(buffer[8] == 0);
	
	s.reset();
	assert(s.addx32(0) == false);
	assert(buffer[0]  == '0');
	assert(buffer[1] == 0);
	
	s.reset();
	assert(s.addx32(0, true) == false);
	assert(buffer[0]  == '0');
	assert(buffer[1]  == '0');
	assert(buffer[2]  == '0');
	assert(buffer[3]  == '0');
	assert(buffer[4]  == '0');
	assert(buffer[5]  == '0');
	assert(buffer[6]  == '0');
	assert(buffer[7]  == '0');
	assert(buffer[8] == 0);
	
	s.reset();
	assert(s.addx32(0x80486) == false);
	assert(buffer[0]  == '8');
	assert(buffer[1]  == '0');
	assert(buffer[2]  == '4');
	assert(buffer[3]  == '8');
	assert(buffer[4]  == '6');
	assert(buffer[5] == 0);
	
	// addx64
	
	s.reset();
	assert(s.addx64(0xdd86_c0ff_ee08_0486) == false);
	assert(buffer[0]  == 'd');
	assert(buffer[1]  == 'd');
	assert(buffer[2]  == '8');
	assert(buffer[3]  == '6');
	assert(buffer[4]  == 'c');
	assert(buffer[5]  == '0');
	assert(buffer[6]  == 'f');
	assert(buffer[7]  == 'f');
	assert(buffer[8]  == 'e');
	assert(buffer[9]  == 'e');
	assert(buffer[10] == '0');
	assert(buffer[11] == '8');
	assert(buffer[12] == '0');
	assert(buffer[13] == '4');
	assert(buffer[14] == '8');
	assert(buffer[15] == '6');
	assert(buffer[16] == 0);
	
	s.reset();
	assert(s.addx64(0xbb) == false);
	assert(buffer[0]  == 'b');
	assert(buffer[1]  == 'b');
	assert(buffer[2]  == 0);
	
	s.reset();
	assert(s.addx64(0xdd, true) == false);
	assert(buffer[0]  == '0');
	assert(buffer[1]  == '0');
	assert(buffer[2]  == '0');
	assert(buffer[3]  == '0');
	assert(buffer[4]  == '0');
	assert(buffer[5]  == '0');
	assert(buffer[6]  == '0');
	assert(buffer[7]  == '0');
	assert(buffer[8]  == '0');
	assert(buffer[9]  == '0');
	assert(buffer[10] == '0');
	assert(buffer[11] == '0');
	assert(buffer[12] == '0');
	assert(buffer[13] == '0');
	assert(buffer[14] == 'd');
	assert(buffer[15] == 'd');
	assert(buffer[16] == 0);
	
	s.reset();
	assert(s.addx64(0) == false);
	assert(buffer[0]  == '0');
	assert(buffer[1] == 0);
	
	s.reset();
	assert(s.addx64(0, true) == false);
	assert(buffer[0]  == '0');
	assert(buffer[1]  == '0');
	assert(buffer[2]  == '0');
	assert(buffer[3]  == '0');
	assert(buffer[4]  == '0');
	assert(buffer[5]  == '0');
	assert(buffer[6]  == '0');
	assert(buffer[7]  == '0');
	assert(buffer[8]  == '0');
	assert(buffer[9]  == '0');
	assert(buffer[10] == '0');
	assert(buffer[11] == '0');
	assert(buffer[12] == '0');
	assert(buffer[13] == '0');
	assert(buffer[14] == '0');
	assert(buffer[15] == '0');
	assert(buffer[16] == 0);
	
	s.reset();
	assert(s.addx64(0x80960) == false);
	assert(buffer[0]  == '8');
	assert(buffer[1]  == '0');
	assert(buffer[2]  == '9');
	assert(buffer[3]  == '6');
	assert(buffer[4]  == '0');
	assert(buffer[5] == 0);
}