module structs; private { import core.exception; import std.array; import std.bitmanip; import std.exception; import std.system; /++ + Supported format types +/ enum FORMAT_TYPE { INT_8, /// Signed integer 8 bit INT_16, /// Signed integer 16 bit INT_32, /// Signed integer 32 bit INT_64, /// Signed integer 64 bit UINT_8, /// Unsigned integer 8 bit UINT_16, /// Unsigned integer 16 bit UINT_32, /// Unsigned integer 32 bit UINT_64, /// Unsigned integer 64 bit STRING /// C-String } /++ + A element of a package +/ struct Element { Endian endian; /// The endian to use bool is_array; /// If it's an array size_t array_size; /// Array size FORMAT_TYPE format_type; /// The formated type /++ + Calculate the size of the element + Returns: Size in bytes +/ size_t packSize() const { // Base format type size size_t base_size = 0; final switch (this.format_type) { case FORMAT_TYPE.STRING: case FORMAT_TYPE.INT_8: case FORMAT_TYPE.UINT_8: base_size = 1; break; case FORMAT_TYPE.INT_16: case FORMAT_TYPE.UINT_16: base_size = 2; break; case FORMAT_TYPE.INT_32: case FORMAT_TYPE.UINT_32: base_size = 4; break; case FORMAT_TYPE.INT_64: case FORMAT_TYPE.UINT_64: base_size = 8; break; } // Add array size return base_size * this.array_size; } static pure Element genFromString(string data) in (data.length > 0, "Format can't be empty.") { // Pre checks size_t current_pos = 0; // Read endian bool endian_explicit = false; Endian endian_found = std.system.endian; switch (data[0]) { case '=': endian_found = std.system.endian; current_pos++; endian_explicit = true; break; case '<': endian_found = Endian.littleEndian; current_pos++; endian_explicit = true; break; case '!': case '>': endian_found = Endian.bigEndian; current_pos++; endian_explicit = true; break; default: } // Get size bool set_size = false; size_t size = 0; while ((current_pos < data.length) && ('0' <= data[current_pos]) && (data[current_pos] <= '9')) { set_size = true; size = (size * 10) + (data[current_pos] - '0'); current_pos++; } if (!set_size) { size = 1; } // Get format assert(current_pos + 1 == data.length, "Format '" ~ data[current_pos .. $] ~ "' isn't a valid format type."); FORMAT_TYPE format_type_found; switch (data[current_pos]) { case 'b': format_type_found = FORMAT_TYPE.INT_8; current_pos++; break; case 'B': format_type_found = FORMAT_TYPE.UINT_8; current_pos++; break; case 'h': format_type_found = FORMAT_TYPE.INT_16; current_pos++; break; case 'H': format_type_found = FORMAT_TYPE.UINT_16; current_pos++; break; case 'i': format_type_found = FORMAT_TYPE.INT_32; current_pos++; break; case 'I': format_type_found = FORMAT_TYPE.UINT_32; current_pos++; break; case 'q': format_type_found = FORMAT_TYPE.INT_64; current_pos++; break; case 'Q': format_type_found = FORMAT_TYPE.UINT_64; current_pos++; break; case 's': assert(set_size, "Size have to be set for the string."); format_type_found = FORMAT_TYPE.STRING; current_pos++; break; default: assert(false, "Unknown format string: '" ~ data[current_pos .. $] ~ "'"); } // TODO: Warn if endian isn't set explicit // Return new struct assert(current_pos == data.length); Element result = {endian: endian_found, is_array: set_size, array_size: size, format_type: format_type_found}; return result; } } template GET_TYPE(Element ELEMENT) { static if (ELEEMENT.format_type == FORMAT_TYPE.STRING) { alias GET_TYPE = string; } else { static assert(false); } } /++ + Removes the whitespaces of the string. + Params: + source = The source string to format + Returns: String without whitespaces +/ pure string remove_whitespaces(string source) { return source.replace(" ", ""); } pure Element[] parse_string(string source) { // Remove whitespaces source = remove_whitespaces(source); // Split after char Element[] elements = []; size_t pos = 0; size_t last = 0; while (pos < source.length) { if ((source[pos] >= 'a' && source[pos] <= 'z') || (source[pos] >= 'A' && source[pos] <= 'Z')) { elements ~= [Element.genFromString(source[last .. pos + 1])]; last = pos + 1; } pos++; } assert(last == pos, "Format doesn't end correctly."); return elements; } // Test remove whitespaces. unittest { assert(remove_whitespaces(" a b c ") == "abc"); } // Test endian and length unittest { { const auto tmp = Element.genFromString("i"); assert(tmp.endian == endian); assert(tmp.is_array == false); assert(tmp.array_size == 1); assert(tmp.format_type == FORMAT_TYPE.INT_32); } { const auto tmp = Element.genFromString("=i"); assert(tmp.endian == endian); assert(tmp.is_array == false); assert(tmp.array_size == 1); assert(tmp.format_type == FORMAT_TYPE.INT_32); } { const auto tmp = Element.genFromString(">i"); assert(tmp.endian == Endian.bigEndian); assert(tmp.is_array == false); assert(tmp.array_size == 1); assert(tmp.format_type == FORMAT_TYPE.INT_32); } { const auto tmp = Element.genFromString("!i"); assert(tmp.endian == Endian.bigEndian); assert(tmp.is_array == false); assert(tmp.array_size == 1); assert(tmp.format_type == FORMAT_TYPE.INT_32); } { const auto tmp = Element.genFromString("16i=16i!16i"); foreach (i; tmp) { assert(i.is_array == true); assert(i.array_size == 16); assert(i.format_type == FORMAT_TYPE.INT_32); } } assertThrown!AssertError(parse_string("i16")); assertThrown!AssertError(parse_string("i!")); assertThrown!AssertError(parse_string("16!i")); } } public { /++ + Base format type +/ struct BaseFormat(string CONFIG) { private { static const(Element[]) elements = parse_string(CONFIG); static const(size_t) elements_size = { size_t size = 0; static foreach (i; elements) { size += i.packSize(); } return size; }(); } public { /++ + Calculate the size of the format. + Returns: Size of the format +/ static size_t size() { return elements_size; } } } } private { size_t test_size(string FORMAT)() { size_t result = BaseFormat!FORMAT.size(); { auto tmp = parse_string(FORMAT); size_t calced = 0; foreach (i; tmp) { calced += i.packSize(); } assert(result == calced); } return result; } unittest { assert(test_size!"10s"() == 10); assert(test_size!"b"() == 1); assert(test_size!"4b"() == 4); assert(test_size!"B"() == 1); assert(test_size!"4B"() == 4); assert(test_size!"h"() == 2); assert(test_size!"4h"() == 8); assert(test_size!"H"() == 2); assert(test_size!"4H"() == 8); assert(test_size!"i"() == 4); assert(test_size!"4i"() == 16); assert(test_size!"I"() == 4); assert(test_size!"4I"() == 16); assert(test_size!"q"() == 8); assert(test_size!"4q"() == 32); assert(test_size!"Q"() == 8); assert(test_size!"4Q"() == 32); } }