1 /*******************************************************************************
2  * 
3  * Functions for formatting data into strings and back.
4  * 
5  * Authors:
6  *   $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise)
7  * 
8  * Copyright:
9  *   © 2015 $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise)
10  * 
11  * License:
12  *   $(LINK2 http://www.gnu.org/licenses/gpl-3.0, GNU General Public License 3.0)
13  * 
14  **************************************/
15 module fast.format;
16 
17 import fast.helpers;
18 import std.string;
19 import std.traits;
20 import std.typecons;
21 import std.typetuple;
22 import core.stdc.stdlib;
23 import core.stdc.string;
24 import core.bitop;
25 
26 
27 /+
28  ╔══════════════════════════════════════════════════════════════════════════════
29  ║ ⚑ Hex String
30  ╚══════════════════════════════════════════════════════════════════════════════
31  +/
32 
33 /**
34  * Converts an unsigned type into a fixed width 8 digits hex string using lower-case letters.
35  * 
36  * Params:
37  *   n = the number to convert
38  * 
39  * Returns:
40  *   hexadecimal representation of $(D n), lower-case letters
41  */
42 @safe pure nothrow @nogc
43 char[2 * U.sizeof] hexStrLower(U)(Unqual!U n) if (isUnsigned!U)
44 {
45 	char[2 * U.sizeof] hex = void;
46 	foreach_reverse (i; 0 .. 2 * U.sizeof)
47 	{
48 		U d = n & U(0xF);
49 		hex[i] = cast(char) (d < 10 ? '0' + d : 'a' + d - 10);
50 		n >>= 4;
51 	}
52 	return hex;
53 }
54 
55 
56 /**
57  * Converts an unsigned type into a fixed width 8 digits hex string using upper-case letters.
58  * 
59  * Params:
60  *   n = the number to convert
61  * 
62  * Returns:
63  *   hexadecimal representation of $(D n), upper-case letters
64  */
65 @safe pure nothrow @nogc
66 char[2 * U.sizeof] hexStrUpper(U)(U n) if (isUnsigned!U)
67 {
68 	char[2 * U.sizeof] hex = void;
69 	foreach_reverse (i; 0 .. 2 * U.sizeof)
70 	{
71 		U d = n & U(0xF);
72 		hex[i] = cast(char) (d < 10 ? '0' + d : 'A' + d - 10);
73 		n >>= 4;
74 	}
75 	return hex;
76 }
77 
78 
79 /+
80  ╔══════════════════════════════════════════════════════════════════════════════
81  ║ ⚑ Decimal String
82  ╚══════════════════════════════════════════════════════════════════════════════
83  +/
84 
85 template decDigits(T) if (isIntegral!T)
86 {
87 	static if (is(T == ulong))
88 		enum decDigits = 20;
89 	else static if (is(T == long))
90 		enum decDigits = 19;
91 	else static if (is(T == uint) || is(T == int))
92 		enum decDigits = 10;
93 	else static if (is(T == ushort) || is(T == short))
94 		enum decDigits = 5;
95 	else static if (is(T == ubyte) || is(T == byte))
96 		enum decDigits = 3;
97 }
98 
99 
100 enum decChars(T) = decDigits!T + isSigned!T;
101 
102 
103 @safe pure nothrow @nogc
104 RevFillStr!(decChars!I) decStr(I)(I i) if (isIntegral!I)
105 {
106 	RevFillStr!(decChars!I) str;
107 	size_t idx = decChars!I;
108 
109 	static if (isSigned!I)
110 	{
111 		bool signed = i < 0;
112 		UnsignedOf!I u = i < 0 ? -i : i;
113 	}
114 	else alias u = i;
115 
116 	do
117 	{
118 		str ~= char('0' + u % 10);
119 		u /= 10;
120 	}
121 	while (u);
122 
123 	static if (isSigned!I) if (signed)
124 		str ~= '-';
125 
126 	return str;
127 }
128 
129 
130 /+
131  ╔══════════════════════════════════════════════════════════════════════════════
132  ║ ⚑ Formatting
133  ╚══════════════════════════════════════════════════════════════════════════════
134  +/
135 
136 template hasKnownSpaceRequirement(T)
137 {
138 	static if (isIntegral!T || isPointer!T)
139 		enum hasKnownSpaceRequirement = true;
140 	else
141 		enum hasKnownSpaceRequirement = false;
142 }
143 
144 
145 template spaceRequirement(string format, T) if (hasKnownSpaceRequirement!T)
146 {
147 	static if (isIntegral!T)
148 	{
149 		static if (format == "%s" || format == "%d")
150 			enum spaceRequirement = decChars!T;
151 		else static if (isUnsigned!T && (format == "%x" || format == "%X"))
152 			enum spaceRequirement = 2 * T.sizeof;
153 		else static assert (0, "Don't know how to handle " ~ T.stringof ~ " as " ~ format);
154 	}
155 	else static if (isPointer!T)
156 	{
157 		static if (format == "%s" || format == "%p")
158 			enum spaceRequirement = 2 * T.sizeof;
159 		else static assert (0, "Don't know how to handle " ~ T.stringof ~ " as " ~ format);
160 	}
161 	else static assert (0, "Don't know how to handle " ~ T.stringof);
162 }
163 
164 
165 enum spaceRequirements(string format, Args...)() if (allSatisfy!(hasKnownSpaceRequirement, Args))
166 {
167 	size_t sum = 0;
168 	
169 	alias parts = tokenizedFormatString!format;
170 	foreach (i; staticIota!(0, parts.length))
171 	{
172 		static if (parts[i][1] == size_t.max)
173 			sum += parts[i][0].length;
174 		else
175 			sum += spaceRequirement!(parts[i][0], Args[parts[i][1]]);
176 	}
177 	
178 	return sum;
179 }
180 
181 
182 template tokenizedFormatString(string format)
183 {
184 	enum impl()
185 	{
186 		Tuple!(string, size_t)[] parts;
187 		size_t i = 0;
188 		string rest = format;
189 
190 		while (1)
191 		{
192 			ptrdiff_t markerPos = rest.indexOf("%");
193 			if (markerPos < 0)
194 				return rest.length ? parts ~ tuple(rest, size_t.max) : parts;
195 
196 			if (markerPos)
197 			{
198 				parts ~= tuple(rest[0 .. markerPos], size_t.max);
199 				rest = rest[markerPos .. $];
200 			}
201 
202 			// TODO: more complex formats
203 			parts ~= tuple(rest[0 .. 2], i++);
204 			rest = rest[2 .. $];
205 		}
206 	}
207 
208 	enum result = impl();
209 	static immutable Tuple!(string, size_t)[result.length] tokenizedFormatString = result;
210 }
211 
212 
213 enum formatStringArgCount(string format)()
214 {
215 	size_t count = 0;
216 
217 	alias parts = tokenizedFormatString!format;
218 	foreach (i; staticIota!(0, parts.length))
219 		if (parts[i][1] != size_t.max && parts[i][1] >= count)
220 			count = parts[i][1] + 1;
221 
222 	return count;
223 }
224 
225 
226 template format(string fmt)
227 {
228 	enum argCnt = formatStringArgCount!fmt;
229 	
230 	enum codeGen()
231 	{
232 		string code = `pure nothrow string format(`;
233 		foreach (i; staticIota!(0, argCnt))
234 		{
235 			if (i) code ~= `, `;
236 			code ~= std..string.format("A%s", i);
237 		}
238 		code ~= `)(`;
239 		foreach (i; staticIota!(0, argCnt))
240 		{
241 			if (i) code ~= `, `;
242 			code ~= std..string.format("A%s a%s", i, i);
243 		}
244 		code ~= `, char[] buffer = new char[](spaceRequirements!(fmt`;
245 		foreach (i; staticIota!(0, argCnt))
246 		code ~= std..string.format(", A%s", i);
247 		code ~= `))) { return std.exception.assumeUnique(formattedWrite!fmt(buffer.ptr`;
248 		foreach (i; staticIota!(0, argCnt))
249 		code ~= std..string.format(", a%s", i);
250 		code ~= `)); }`;
251 		return code;
252 	}
253 	
254 	mixin(codeGen());
255 }
256 
257 
258 template formata(string fmt)
259 {
260 	enum argCnt = formatStringArgCount!fmt;
261 
262 	enum codeGen()
263 	{
264 		string code = `pure nothrow @nogc char[] formata(`;
265 		foreach (i; staticIota!(0, argCnt))
266 		{
267 			if (i) code ~= `, `;
268 			code ~= std..string.format("A%s", i);
269 		}
270 		code ~= `)(`;
271 		foreach (i; staticIota!(0, argCnt))
272 		{
273 			if (i) code ~= `, `;
274 			code ~= std..string.format("A%s a%s", i, i);
275 		}
276 		code ~= `, void* buffer = alloca(spaceRequirements!(fmt`;
277 		foreach (i; staticIota!(0, argCnt))
278 			code ~= std..string.format(", A%s", i);
279 		code ~= `))) { return formattedWrite!fmt(cast(char*) buffer`;
280 		foreach (i; staticIota!(0, argCnt))
281 			code ~= std..string.format(", a%s", i);
282 		code ~= `); }`;
283 		return code;
284 	}
285 
286 	mixin(codeGen());
287 }
288 
289 
290 template formats(string fmt)
291 {
292 	enum argCnt = formatStringArgCount!fmt;
293 	
294 	enum codeGen()
295 	{
296 		string code = `@safe pure nothrow @nogc auto formats(`;
297 		foreach (i; staticIota!(0, argCnt))
298 		{
299 			if (i) code ~= `, `;
300 			code ~= std..string.format("A%s", i);
301 		}
302 		code ~= `)(`;
303 		foreach (i; staticIota!(0, argCnt))
304 		{
305 			if (i) code ~= `, `;
306 			code ~= std..string.format("A%s a%s", i, i);
307 		}
308 		code ~= `))) { LimitedScopeBuffer!(char, spaceRequirements!(fmt`;
309 		foreach (i; staticIota!(0, argCnt))
310 			code ~= std..string.format(", A%s", i);
311 		code ~= `)) buffer; buffer.length = formattedWrite!fmt(buffer.ptr`;
312 		foreach (i; staticIota!(0, argCnt))
313 			code ~= std..string.format(", a%s", i);
314 		code ~= `).length; return buffer; }`;
315 		return code;
316 	}
317 	
318 	mixin(codeGen());
319 }
320 
321 
322 char[] formattedWrite(string format, Args...)(char* buffer, Args args)
323 {
324 	char* it = buffer;
325 
326 	alias parts = tokenizedFormatString!format;
327 	foreach (i; staticIota!(0, parts.length))
328 	{
329 		static if (parts[i][1] == size_t.max)
330 		{
331 			// Direct string copy
332 			memcpy( it, parts[i][0].ptr, parts[i][0].length );
333 			it += parts[i][0].length;
334 		}
335 		else
336 		{
337 			// Formatted argument
338 			it.formattedWriteItem!(parts[i][0])( args[parts[i][1]] );
339 		}
340 	}
341 
342 	return buffer[0 .. it - buffer];
343 }
344 
345 
346 pure nothrow @nogc
347 void formattedWriteItem(string format, T)(ref char* buffer, T t)
348 	if (isUnsigned!T && format == "%x")
349 {
350 	alias RT = ReturnType!(hexStrLower!T);
351 	*cast(RT*) buffer = hexStrLower!T(t);
352 	buffer += RT.length;
353 }
354 
355 
356 pure nothrow @nogc
357 void formattedWriteItem(string format, T)(ref char* buffer, T t)
358 	if (isUnsigned!T && format == "%X")
359 {
360 	alias RT = ReturnType!(hexStrUpper!T);
361 	*cast(RT*) buffer = hexStrUpper!T(t);
362 	buffer += RT.length;
363 }
364 
365 
366 pure nothrow @nogc
367 void formattedWriteItem(string format, T)(ref char* buffer, T t)
368 	if (isIntegral!T && (format == "%s" || format == "%d"))
369 {
370 	auto str = decStr(t);
371 	memcpy( buffer, str.ptr, str.length );
372 	buffer += str.length;
373 }
374 
375 
376 pure nothrow @nogc
377 void formattedWriteItem(string format)(ref char* buffer, void* p)
378 	if (format == "%s" || format == "%p")
379 {
380 	buffer.formattedWriteItem!"%X"( cast(size_t) p );
381 }
382 
383 
384 /+
385  ╔══════════════════════════════════════════════════════════════════════════════
386  ║ ⚑ Helper Structs
387  ╚══════════════════════════════════════════════════════════════════════════════
388  +/
389 
390 struct RevFillStr(size_t n)
391 {
392 private:
393 
394 	size_t offset = n;
395 	char[n] buffer = '\0';
396 
397 
398 public:
399 
400 	alias opSlice this;
401 
402 	@safe pure nothrow @nogc
403 	void opOpAssign(string op : "~")(char ch)
404 	in
405 	{
406 		assert( offset > 0 );
407 	}
408 	body
409 	{
410 		buffer[--offset] = ch;
411 	}
412 
413 
414 	@safe pure nothrow @nogc
415 	@property inout(char)[] opSlice() inout
416 	{
417 		return buffer[offset .. n];
418 	}
419 
420 
421 	@safe pure nothrow @nogc
422 	@property inout(char)* ptr() inout
423 	{
424 		return &buffer[offset];
425 	}
426 
427 
428 	@safe pure nothrow @nogc
429 	@property size_t length() const
430 	{
431 		return n - offset;
432 	}
433 }