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 }