/// Handles data formatting. /// Copyright: dd86k <dd@dax.moe> /// License: MIT /// Authors: $(LINK2 https://github.com/dd86k, dd86k) module formatter; //TODO: format function for int size_t format8hex(char *buffer, ubyte v) { buffer[1] = hexMap[v & 15]; buffer[0] = hexMap[v >> 4]; return 2; } @system unittest { char[2] c = void; format02x(c.ptr, 0x01); assert(c[] == "01", c); format02x(c.ptr, 0x20); assert(c[] == "20", c); format02x(c.ptr, 0xff); assert(c[] == "ff", c); } size_t format64hex(char *buffer, ulong v) { size_t pos; bool pad = true; for (int shift = 60; shift >= 0; shift -= 4) { const ubyte b = (v >> shift) & 15; if (b == 0) { if (pad && shift >= 44) { continue; // cut } else if (pad && shift >= 4) { buffer[pos++] = pad ? ' ' : '0'; continue; // pad } } else // Padding no longer acceptable pad = false; buffer[pos++] = hexMap[b]; } return pos; } @system unittest { char[32] b = void; char *p = b.ptr; assert(b[0..format64hex(p, 0)] == " 0"); assert(b[0..format64hex(p, 1)] == " 1"); assert(b[0..format64hex(p, 0x10)] == " 10"); assert(b[0..format64hex(p, 0x100)] == " 100"); assert(b[0..format64hex(p, 0x1000)] == " 1000"); assert(b[0..format64hex(p, 0x10000)] == " 10000"); assert(b[0..format64hex(p, 0x100000)] == " 100000"); assert(b[0..format64hex(p, 0x1000000)] == " 1000000"); assert(b[0..format64hex(p, 0x10000000)] == " 10000000"); assert(b[0..format64hex(p, 0x100000000)] == " 100000000"); assert(b[0..format64hex(p, 0x1000000000)] == " 1000000000"); assert(b[0..format64hex(p, 0x10000000000)] == "10000000000"); assert(b[0..format64hex(p, 0x100000000000)] == "100000000000"); assert(b[0..format64hex(p, 0x1000000000000)] == "1000000000000"); assert(b[0..format64hex(p, ubyte.max)] == " ff"); assert(b[0..format64hex(p, ushort.max)] == " ffff"); assert(b[0..format64hex(p, uint.max)] == " ffffffff"); assert(b[0..format64hex(p, ulong.max)] == "ffffffffffffffff"); assert(b[0..format64hex(p, 0x1010)] == " 1010"); assert(b[0..format64hex(p, 0x10101010)] == " 10101010"); assert(b[0..format64hex(p, 0x1010101010101010)] == "1010101010101010"); } private: immutable string hexMap = "0123456789abcdef"; size_t format02x(char *buffer, ubyte v) { buffer[1] = hexMap[v & 15]; buffer[0] = hexMap[v >> 4]; return 2; } @system unittest { char[2] c = void; format02x(c.ptr, 0x01); assert(c[] == "01", c); format02x(c.ptr, 0x20); assert(c[] == "20", c); format02x(c.ptr, 0xff); assert(c[] == "ff", c); } size_t format11x(char *buffer, long v) { size_t pos; bool pad = true; for (int shift = 60; shift >= 0; shift -= 4) { const ubyte b = (v >> shift) & 15; if (b == 0) { if (pad && shift >= 44) { continue; // cut } else if (pad && shift >= 4) { buffer[pos++] = pad ? ' ' : '0'; continue; // pad } } else pad = false; buffer[pos++] = hexMap[b]; } return pos; } /// @system unittest { char[32] b = void; char *p = b.ptr; assert(b[0..format11x(p, 0)] == " 0"); assert(b[0..format11x(p, 1)] == " 1"); assert(b[0..format11x(p, 0x10)] == " 10"); assert(b[0..format11x(p, 0x100)] == " 100"); assert(b[0..format11x(p, 0x1000)] == " 1000"); assert(b[0..format11x(p, 0x10000)] == " 10000"); assert(b[0..format11x(p, 0x100000)] == " 100000"); assert(b[0..format11x(p, 0x1000000)] == " 1000000"); assert(b[0..format11x(p, 0x10000000)] == " 10000000"); assert(b[0..format11x(p, 0x100000000)] == " 100000000"); assert(b[0..format11x(p, 0x1000000000)] == " 1000000000"); assert(b[0..format11x(p, 0x10000000000)] == "10000000000"); assert(b[0..format11x(p, 0x100000000000)] == "100000000000"); assert(b[0..format11x(p, 0x1000000000000)] == "1000000000000"); assert(b[0..format11x(p, ubyte.max)] == " ff"); assert(b[0..format11x(p, ushort.max)] == " ffff"); assert(b[0..format11x(p, uint.max)] == " ffffffff"); assert(b[0..format11x(p, ulong.max)] == "ffffffffffffffff"); assert(b[0..format11x(p, 0x1010)] == " 1010"); assert(b[0..format11x(p, 0x10101010)] == " 10101010"); assert(b[0..format11x(p, 0x1010101010101010)] == "1010101010101010"); } immutable static string decMap = "0123456789"; size_t format03d(char *buffer, ubyte v) { buffer[2] = (v % 10) + '0'; buffer[1] = (v / 10 % 10) + '0'; buffer[0] = (v / 100 % 10) + '0'; return 3; } @system unittest { char[3] c = void; format03d(c.ptr, 1); assert(c[] == "001", c); format03d(c.ptr, 10); assert(c[] == "010", c); format03d(c.ptr, 111); assert(c[] == "111", c); } size_t format11d(char *buffer, long v) { debug import std.conv : text; enum ulong I64MAX = 10_000_000_000_000_000_000UL; size_t pos; bool pad = true; for (ulong d = I64MAX; d > 0; d /= 10) { const long r = (v / d) % 10; if (r == 0) { if (pad && d >= 100_000_000_000) { continue; // cut } else if (pad && d >= 10) { buffer[pos++] = pad ? ' ' : '0'; continue; } } else pad = false; debug assert(r >= 0 && r < 10, "r="~r.text); buffer[pos++] = decMap[r]; } return pos; } /// @system unittest { char[32] b = void; char *p = b.ptr; assert(b[0..format11d(p, 0)] == " 0"); assert(b[0..format11d(p, 1)] == " 1"); assert(b[0..format11d(p, 10)] == " 10"); assert(b[0..format11d(p, 100)] == " 100"); assert(b[0..format11d(p, 1000)] == " 1000"); assert(b[0..format11d(p, 10_000)] == " 10000"); assert(b[0..format11d(p, 100_000)] == " 100000"); assert(b[0..format11d(p, 1000_000)] == " 1000000"); assert(b[0..format11d(p, 10_000_000)] == " 10000000"); assert(b[0..format11d(p, 100_000_000)] == " 100000000"); assert(b[0..format11d(p, 1000_000_000)] == " 1000000000"); assert(b[0..format11d(p, 10_000_000_000)] == "10000000000"); assert(b[0..format11d(p, 100_000_000_000)] == "100000000000"); assert(b[0..format11d(p, 1000_000_000_000)] == "1000000000000"); assert(b[0..format11d(p, ubyte.max)] == " 255"); assert(b[0..format11d(p, ushort.max)] == " 65535"); assert(b[0..format11d(p, uint.max)] == " 4294967295"); assert(b[0..format11d(p, ulong.max)] == "18446744073709551615"); assert(b[0..format11d(p, 1010)] == " 1010"); } size_t format03o(char *buffer, ubyte v) { buffer[2] = (v % 8) + '0'; buffer[1] = (v / 8 % 8) + '0'; buffer[0] = (v / 64 % 8) + '0'; return 3; } @system unittest { import std.conv : octal; char[3] c = void; format03o(c.ptr, 1); assert(c[] == "001", c); format03o(c.ptr, octal!20); assert(c[] == "020", c); format03o(c.ptr, octal!133); assert(c[] == "133", c); } size_t format11o(char *buffer, long v) { size_t pos; if (v >> 63) buffer[pos++] = '1'; // ulong.max coverage bool pad = true; for (int shift = 60; shift >= 0; shift -= 3) { const ubyte b = (v >> shift) & 7; if (b == 0) { if (pad && shift >= 33) { continue; // cut } else if (pad && shift >= 3) { buffer[pos++] = pad ? ' ' : '0'; continue; } } else pad = false; buffer[pos++] = hexMap[b]; } return pos; } /// @system unittest { import std.conv : octal; char[32] b = void; char *p = b.ptr; assert(b[0..format11o(p, 0)] == " 0"); assert(b[0..format11o(p, 1)] == " 1"); assert(b[0..format11o(p, octal!10)] == " 10"); assert(b[0..format11o(p, octal!20)] == " 20"); assert(b[0..format11o(p, octal!100)] == " 100"); assert(b[0..format11o(p, octal!1000)] == " 1000"); assert(b[0..format11o(p, octal!10_000)] == " 10000"); assert(b[0..format11o(p, octal!100_000)] == " 100000"); assert(b[0..format11o(p, octal!1000_000)] == " 1000000"); assert(b[0..format11o(p, octal!10_000_000)] == " 10000000"); assert(b[0..format11o(p, octal!100_000_000)] == " 100000000"); assert(b[0..format11o(p, octal!1000_000_000)] == " 1000000000"); assert(b[0..format11o(p, octal!10_000_000_000)] == "10000000000"); assert(b[0..format11o(p, octal!100_000_000_000)] == "100000000000"); assert(b[0..format11o(p, ubyte.max)] == " 377"); assert(b[0..format11o(p, ushort.max)] == " 177777"); assert(b[0..format11o(p, uint.max)] == "37777777777"); assert(b[0..format11o(p, ulong.max)] == "1777777777777777777777"); assert(b[0..format11o(p, octal!101_010)] == " 101010"); } // !SECTION version (none): //int outputLine(long base, ubyte[] data, int row, int cursor = -1) //TODO: Add int param for data at cursor (placeholder) /// Render multiple lines on screen with optional cursor. /// Params: /// base = Offset base. /// data = data to render. /// cursor = Position of cursor. /// Returns: Number of rows printed. Negative numbers indicate error. int output(long base, ubyte[] data, int cursor = -1) { int crow = void, ccol = void; if (data.length == 0) return 0; if (cursor < 0) crow = ccol = -1; else { crow = cursor / setting.columns; ccol = cursor % setting.columns; } version (Trace) { trace("base=%u D=%u crow=%d ccol=%d", base, data.length, crow, ccol); StopWatch sw = StopWatch(AutoStart.yes); } size_t buffersz = // minimum anyway OFFSET_SPACE + 2 + // offset + spacer ((binaryFormatter.size + 1) * setting.columns) + // binary + spacer * cols (1 + (setting.columns * 3)); // spacer + text (utf-8) char *buffer = cast(char*)malloc(buffersz); if (buffer == null) return -1; int lines; foreach (chunk; chunks(data, setting.columns)) { const bool cur_row = lines == crow; Row row = makerow(buffer, buffersz, chunk, base, ccol); if (cur_row) { version (Trace) trace( "row.length=%u cbi=%u cbl=%u cti=%u ctl=%u bl=%u tl=%u", row.result.length, row.cursorBinaryIndex, row.cursorBinaryLength, row.cursorTextIndex, row.cursorTextLength, row.binaryLength, row.textLength); // between binary and text cursors size_t distance = row.cursorTextIndex - row.cursorBinaryIndex - row.cursorBinaryLength; char *p = buffer; // offset + pre-cursor binary p += cwrite(p, row.cursorBinaryIndex); // binary cursor terminalInvertColor; p += cwrite(p, row.cursorBinaryLength); terminalResetColor; // post-cursor binary + pre-cursor text (minus spacer) p += cwrite(p, distance); // text cursor terminalHighlight; p += cwrite(p, row.cursorTextLength); terminalResetColor; // post-cursor text size_t rem = row.result.length - (p - buffer); p += cwrite(p, rem); version (Trace) trace("d=%u r=%u l=%u", distance, rem, p - buffer); } else cwrite(row.result.ptr, row.result.length); cwrite('\n'); ++lines; base += setting.columns; } free(buffer); version (Trace) { sw.stop; trace("time='%s µs'", sw.peek.total!"usecs"); } return lines; } //TODO: Consider moving to this ddhx void renderEmpty(uint rows, int w) { version (Trace) { trace("lines=%u rows=%u cols=%u", lines, rows, w); StopWatch sw = StopWatch(AutoStart.yes); } char *p = cast(char*)malloc(w); assert(p); //TODO: Soft asserts memset(p, ' ', w); //TODO: Output to scoped OutBuffer for (int i; i < rows; ++i) cwrite(p, w); free(p); version (Trace) { sw.stop; trace("time='%s µs'", sw.peek.total!"usecs"); } } private struct Row { char[] result; size_t cursorBinaryIndex; size_t cursorBinaryLength; size_t cursorTextIndex; size_t cursorTextLength; size_t binaryLength; size_t textLength; } private Row makerow(char *buffer, size_t bufferlen, ubyte[] chunk, long pos, int cursor_col) { Row row = void; // Insert OFFSET size_t indexData = offsetFormatter.offset(buffer, pos); buffer[indexData++] = ' '; // index: OFFSET + space const uint dataLen = (setting.columns * (binaryFormatter.size + 1)); /// data row character count size_t indexChar = indexData + dataLen; // Position for character column *(cast(ushort*)(buffer + indexChar)) = 0x2020; // DATA-CHAR spacer indexChar += 2; // indexChar: indexData + dataLen + spacer // Format DATA and CHAR // NOTE: Smaller loops could fit in cache... // And would separate data/text logic size_t bi0 = indexData, ti0 = indexChar; int currentCol; foreach (data; chunk) { const bool curhit = currentCol == cursor_col; // cursor hit column //TODO: Maybe binary data formatter should include space? // Data translation buffer[indexData++] = ' '; if (curhit) { row.cursorBinaryIndex = indexData; row.cursorBinaryLength = binaryFormatter.size; } indexData += binaryFormatter.data(buffer + indexData, data); // Character translation immutable(char)[] units = transcoder.transform(data); if (curhit) { row.cursorTextIndex = indexChar; row.cursorTextLength = units.length ? units.length : 1; } if (units.length) // Has utf-8 codepoints { foreach (codeunit; units) buffer[indexChar++] = codeunit; } else // Invalid character, insert default character buffer[indexChar++] = setting.defaultChar; ++currentCol; } row.binaryLength = dataLen - bi0; row.textLength = indexChar - ti0; size_t end = indexChar; // data length < minimum row requirement = in-fill data and text columns if (chunk.length < setting.columns) { // In-fill characters: left = Columns - ChunkLength size_t leftchar = (setting.columns - chunk.length); // Bytes left memset(buffer + indexChar, ' ', leftchar); row.textLength += leftchar; // In-fill binary data: left = CharactersLeft * (DataSize + 1) size_t leftdata = leftchar * (binaryFormatter.size + 1); memset(buffer + indexData, ' ', leftdata); row.binaryLength += leftdata; end += leftchar; } row.result = buffer[0..end]; return row; }