1 module binary.format; 2 3 import std.ascii; 4 import std.algorithm; 5 import std.conv; 6 import std.typetuple; 7 import std.range; 8 import std.traits; 9 import binary.common; 10 11 12 /** 13 * Maps format type character to corresponding D type. 14 * 15 * To get type tuple from format string use `formatTypeTupleOf` instead. 16 * 17 * Any invalid character specified in format string results in void type. 18 * To see supported format specifiers and types, see `pack` function documentation. 19 */ 20 template formatTypeOf(char c) 21 { 22 static if(c == 'c') 23 alias formatTypeOf = char; 24 else static if(c == 'u') 25 alias formatTypeOf = wchar; 26 else static if(c == 'U') 27 alias formatTypeOf = dchar; 28 else static if(c == 'b') 29 alias formatTypeOf = byte; 30 else static if(c == 'B') 31 alias formatTypeOf = ubyte; 32 else static if(c == 'o') 33 alias formatTypeOf = bool; 34 else static if(c == 'h') 35 alias formatTypeOf = short; 36 else static if(c == 'H') 37 alias formatTypeOf = ushort; 38 else static if(c == 'i') 39 alias formatTypeOf = int; 40 else static if(c == 'I') 41 alias formatTypeOf = uint; 42 else static if(c == 'p') 43 alias formatTypeOf = ptrdiff_t; 44 else static if(c == 'P') 45 alias formatTypeOf = size_t; 46 else static if(c == 'l') 47 alias formatTypeOf = long; 48 else static if(c == 'L') 49 alias formatTypeOf = ulong; 50 else static if(c == 'f') 51 alias formatTypeOf = float; 52 else static if(c == 'd') 53 alias formatTypeOf = double; 54 else static if(c == 's') 55 alias formatTypeOf = char[]; 56 else static if(c == 'S') 57 alias formatTypeOf = char[]; 58 else 59 alias formatTypeOf = void; 60 } 61 62 /** 63 * Maps format string to D type tuple. 64 * 65 * Any invalid character specified in format string results in static assert failure. 66 * To see supported format specifiers and types, see `pack` function. 67 */ 68 template formatTypeTupleOf(string format) 69 { 70 static if (format.length < 1) 71 alias formatTypeTupleOf = TypeTuple!(); 72 73 // Whitespaces 74 else static if (isWhite(format[0])) 75 { 76 alias formatTypeTupleOf = formatTypeTupleOf!(format[1..$]); 77 } 78 79 // Pad bytes 80 else static if (formatIsTypeLess!(format[0])) 81 alias formatTypeTupleOf = formatTypeTupleOf!(format[1..$]); 82 83 else static if (format[0] == '*') 84 { 85 static assert(format.length > 1, "Star cannot be last character."); 86 static assert(format[1].isAlpha, "Star must be followed by type character."); 87 88 alias formatTypeTupleOf = TypeTuple!(formatTypeOf!(format[1])[], formatTypeTupleOf!(format[2..$])); 89 } 90 // Repeats 91 else static if (isDigit(format[0])) 92 { 93 // TODO: use formatRepeatCount mixin 94 enum firstNonDigit = countUntil!(a => !isDigit(a))(format); 95 static assert(firstNonDigit != -1, "Digit must be followed by type specifier"); 96 enum count = to!int(format[0..firstNonDigit]); 97 enum type = format[firstNonDigit]; 98 static assert(firstNonDigit == countUntil!isAlpha(format), "Digit cannot be followed by endianess modifier"); 99 100 // Pad bytes are skipped 101 static if (type == 'x' || type == 'X') 102 { 103 static if (firstNonDigit + 1 < format.length) 104 alias formatTypeTupleOf = formatTypeTupleOf!(format[firstNonDigit+1..$]); 105 } 106 else 107 alias formatTypeTupleOf = TypeTuple!(formatTypeOf!(type)[count], formatTypeTupleOf!(format[firstNonDigit+1..$])); 108 } 109 110 // Type chars 111 else static if ( is(formatTypeOf!(format[0])) ) { 112 alias formatTypeTupleOf = TypeTuple!(formatTypeOf!(format[0]), formatTypeTupleOf!(format[1..$])); 113 } 114 else 115 static assert(0, .format("Invalid format character '%c'", format[0])); 116 } 117 118 119 /** 120 * Maps D type to corresponding format type string. 121 * 122 * To get complete format string from TypeTuple, use `formatOf` instead. 123 * 124 * Any unsupported or not mutable type results in static assert failure. 125 * To see supported format specifiers and types, see `pack` function documentaton. 126 */ 127 template formatStringOf(Type) 128 { 129 static if(isSomeString!Type) { 130 enum string formatStringOf = "s"; 131 } 132 else { 133 static if (isDynamicArray!Type) { 134 enum string prefix = "*"; 135 alias T = ElementEncodingType!Type; 136 } 137 else static if (isStaticArray!Type) { 138 enum string prefix = Type.length.stringof; 139 alias T = ElementEncodingType!Type; 140 } 141 else { 142 enum string prefix = ""; 143 alias T = Type; 144 } 145 146 static if(is(T == byte)) 147 enum string formatStringOf = prefix ~ "b"; 148 else static if(is(T == ubyte)) 149 enum string formatStringOf = prefix ~ "B"; 150 else static if(is(T == bool)) 151 enum string formatStringOf = prefix ~ "o"; 152 else static if(is(T == char)) 153 enum string formatStringOf = prefix ~ "c"; 154 else static if(is(T == wchar)) 155 enum string formatStringOf = prefix ~ "u"; 156 else static if(is(T == dchar)) 157 enum string formatStringOf = prefix ~ "U"; 158 else static if(is(T == short)) 159 enum string formatStringOf = prefix ~ "h"; 160 else static if(is(T == ushort)) 161 enum string formatStringOf = prefix ~ "H"; 162 else static if(is(T == int)) 163 enum string formatStringOf = prefix ~ "i"; 164 else static if(is(T == uint)) 165 enum string formatStringOf = prefix ~ "I"; 166 else static if(is(T == long)) 167 enum string formatStringOf = prefix ~ "l"; 168 else static if(is(T == ulong)) 169 enum string formatStringOf = prefix ~ "L"; 170 else static if(is(T == float)) 171 enum string formatStringOf = prefix ~ "f"; 172 else static if(is(T == double)) 173 enum string formatStringOf = prefix ~ "d"; 174 /*else static if(isSomeString!Type) 175 enum char formatCharOf = 's';*/ 176 else 177 static assert(0, "Unsupported type "~ Type.stringof); 178 } 179 } 180 181 182 /** 183 * Creates format specifier for TypeTuple. 184 */ 185 template formatOf(T, V...) 186 { 187 static if (V.length == 0) 188 enum string formatOf = formatStringOf!T; 189 else 190 enum string formatOf = formatStringOf!T ~ formatOf!(V); 191 192 } 193 194 195 /** 196 * Determines if format string character has type equivalent. 197 * 198 * This template does not work with digits however. 199 * 200 * In otherwords this template checks if character is neither endian modifier, 201 * digit nor 'x'. 202 */ 203 template formatIsTypeLess(char c) 204 { 205 enum formatIsTypeLess = (formatCharToEndian!c != -1) || (c == 'x') || (c == 'X'); 206 } 207 208 mixin template formatRepeatCount(string format) 209 { 210 import std.algorithm, std.conv; 211 212 enum resultEndIndex = countUntil!(a => !isDigit(a))(format); 213 static assert(resultEndIndex != -1, "Digit must be followed by type specifier"); 214 enum resultCount = to!int(format[0..resultEndIndex]); 215 enum resultChar = format[resultEndIndex]; 216 enum resultRest = format[resultEndIndex + 1 .. $]; 217 static assert(formatCharToEndian!resultChar == -1, "Digit cannot be followed by endianess modifier"); 218 static assert(!isWhite(resultChar), "Digit cannot be followed by space"); 219 } 220 221 template formatCharToEndian(char c) 222 { 223 static if (c == '<') 224 enum formatCharToEndian = ByteOrder.LittleEndian; 225 else static if (c == '>' || c == '@') 226 enum formatCharToEndian = ByteOrder.BigEndian; 227 else static if (c == '=') 228 enum formatCharToEndian = ByteOrder.Native; 229 else 230 enum formatCharToEndian = -1; 231 } 232 233 234 unittest 235 { 236 static assert(formatStringOf!int != "\0"); 237 static assert(!__traits(compiles, formatStringOf!void)); // does not compile 238 static assert(formatOf!(char, string, byte, ubyte, bool, short, ushort) == "csbBohH"); 239 static assert(formatOf!(int, uint, long, ulong, float, double) == "iIlLfd"); 240 static assert(formatOf!(int[], uint[], long, ulong[], float, double[]) == "*i*Il*Lf*d"); 241 242 static assert(formatCharToEndian!'<' == ByteOrder.LittleEndian); 243 static assert(formatCharToEndian!'>' == ByteOrder.BigEndian); 244 static assert(formatCharToEndian!'@' == ByteOrder.BigEndian); 245 static assert(formatCharToEndian!'=' == ByteOrder.Native); 246 static assert(formatCharToEndian!'%' == -1); 247 static assert(is(formatTypeTupleOf!`hHiI` == TypeTuple!(short, ushort, int, uint))); 248 static assert(is(formatTypeTupleOf!`pPlL` == TypeTuple!(ptrdiff_t, size_t, long, ulong))); 249 static assert(is(formatTypeTupleOf!`csbB` == TypeTuple!(char, char[], byte, ubyte))); 250 static assert(is(formatTypeTupleOf!`obo` == TypeTuple!(bool, byte, bool))); 251 static assert(is(formatTypeTupleOf!`fxd` == TypeTuple!(float, double))); 252 static assert(is(formatTypeTupleOf!`x` == TypeTuple!())); 253 static assert(is(formatTypeTupleOf!`3cx2h`== TypeTuple!(char[3], short[2]))); 254 static assert(is(formatTypeTupleOf!` ` == TypeTuple!())); 255 static assert(is(formatTypeTupleOf!`h 2c` == TypeTuple!(short, char[2]))); 256 static assert(is(formatTypeTupleOf!`h <I 2c` == TypeTuple!(short, uint, char[2]))); 257 static assert(is(formatTypeTupleOf!`*hI*c` == TypeTuple!(short[], uint, char[]))); 258 259 static assert(formatIsTypeLess!'<' == true); 260 static assert(formatIsTypeLess!'x' == true); 261 static assert(formatIsTypeLess!'@' == true); 262 static assert(formatIsTypeLess!'2' == false); /// expected behavior 263 }