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  *   © 2017 $(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 core.stdc.stdlib;
18 import core.stdc..string;
19 import core.bitop;
20 import std..string;
21 import std.traits;
22 import std.typecons;
23 import std.typetuple;
24 import fast.internal.helpers;
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 	import std.exception;
229 
230 	enum argCnt = formatStringArgCount!fmt;
231 
232 	enum codeGen()
233 	{
234 		string code = `pure nothrow string format(`;
235 		foreach (i; staticIota!(0, argCnt))
236 		{
237 			if (i) code ~= `, `;
238 			code ~= std..string.format("A%s", i);
239 		}
240 		code ~= `)(`;
241 		foreach (i; staticIota!(0, argCnt))
242 		{
243 			if (i) code ~= `, `;
244 			code ~= std..string.format("A%s a%s", i, i);
245 		}
246 		code ~= `, char[] buffer = new char[](spaceRequirements!(fmt`;
247 		foreach (i; staticIota!(0, argCnt))
248 		code ~= std..string.format(", A%s", i);
249 		code ~= `))) { return assumeUnique(formattedWrite!fmt(buffer.ptr`;
250 		foreach (i; staticIota!(0, argCnt))
251 		code ~= std..string.format(", a%s", i);
252 		code ~= `)); }`;
253 		return code;
254 	}
255 
256 	mixin(codeGen());
257 }
258 
259 
260 template formata(string fmt)
261 {
262 	enum argCnt = formatStringArgCount!fmt;
263 
264 	enum codeGen()
265 	{
266 		string code = `pure nothrow @nogc char[] formata(`;
267 		foreach (i; staticIota!(0, argCnt))
268 		{
269 			if (i) code ~= `, `;
270 			code ~= std..string.format("A%s", i);
271 		}
272 		code ~= `)(`;
273 		foreach (i; staticIota!(0, argCnt))
274 		{
275 			if (i) code ~= `, `;
276 			code ~= std..string.format("A%s a%s", i, i);
277 		}
278 		code ~= `, void* buffer = alloca(spaceRequirements!(fmt`;
279 		foreach (i; staticIota!(0, argCnt))
280 			code ~= std..string.format(", A%s", i);
281 		code ~= `))) { return formattedWrite!fmt(cast(char*) buffer`;
282 		foreach (i; staticIota!(0, argCnt))
283 			code ~= std..string.format(", a%s", i);
284 		code ~= `); }`;
285 		return code;
286 	}
287 
288 	mixin(codeGen());
289 }
290 
291 
292 template formats(string fmt)
293 {
294 	enum argCnt = formatStringArgCount!fmt;
295 	
296 	enum codeGen()
297 	{
298 		string code = `@safe pure nothrow @nogc auto formats(`;
299 		foreach (i; staticIota!(0, argCnt))
300 		{
301 			if (i) code ~= `, `;
302 			code ~= std..string.format("A%s", i);
303 		}
304 		code ~= `)(`;
305 		foreach (i; staticIota!(0, argCnt))
306 		{
307 			if (i) code ~= `, `;
308 			code ~= std..string.format("A%s a%s", i, i);
309 		}
310 		code ~= `))) { LimitedScopeBuffer!(char, spaceRequirements!(fmt`;
311 		foreach (i; staticIota!(0, argCnt))
312 			code ~= std..string.format(", A%s", i);
313 		code ~= `)) buffer; buffer.length = formattedWrite!fmt(buffer.ptr`;
314 		foreach (i; staticIota!(0, argCnt))
315 			code ~= std..string.format(", a%s", i);
316 		code ~= `).length; return buffer; }`;
317 		return code;
318 	}
319 	
320 	mixin(codeGen());
321 }
322 
323 
324 char[] formattedWrite(string format, Args...)(char* buffer, Args args)
325 {
326 	char* it = buffer;
327 
328 	alias parts = tokenizedFormatString!format;
329 	foreach (i; staticIota!(0, parts.length))
330 	{
331 		static if (parts[i][1] == size_t.max)
332 		{
333 			// Direct string copy
334 			memcpy( it, parts[i][0].ptr, parts[i][0].length );
335 			it += parts[i][0].length;
336 		}
337 		else
338 		{
339 			// Formatted argument
340 			it.formattedWriteItem!(parts[i][0])( args[parts[i][1]] );
341 		}
342 	}
343 
344 	return buffer[0 .. it - buffer];
345 }
346 
347 
348 pure nothrow @nogc
349 void formattedWriteItem(string format, T)(ref char* buffer, T t)
350 	if (isUnsigned!T && format == "%x")
351 {
352 	alias RT = ReturnType!(hexStrLower!T);
353 	*cast(RT*) buffer = hexStrLower!T(t);
354 	buffer += RT.length;
355 }
356 
357 
358 pure nothrow @nogc
359 void formattedWriteItem(string format, T)(ref char* buffer, T t)
360 	if (isUnsigned!T && format == "%X")
361 {
362 	alias RT = ReturnType!(hexStrUpper!T);
363 	*cast(RT*) buffer = hexStrUpper!T(t);
364 	buffer += RT.length;
365 }
366 
367 
368 pure nothrow @nogc
369 void formattedWriteItem(string format, T)(ref char* buffer, T t)
370 	if (isIntegral!T && (format == "%s" || format == "%d"))
371 {
372 	auto str = decStr(t);
373 	memcpy( buffer, str.ptr, str.length );
374 	buffer += str.length;
375 }
376 
377 
378 pure nothrow @nogc
379 void formattedWriteItem(string format)(ref char* buffer, void* p)
380 	if (format == "%s" || format == "%p")
381 {
382 	buffer.formattedWriteItem!"%X"( cast(size_t) p );
383 }
384 
385 
386 /+
387  ╔══════════════════════════════════════════════════════════════════════════════
388  ║ ⚑ Helper Structs
389  ╚══════════════════════════════════════════════════════════════════════════════
390  +/
391 
392 struct RevFillStr(size_t n)
393 {
394 private:
395 
396 	size_t offset = n;
397 	char[n] buffer = '\0';
398 
399 
400 public:
401 
402 	alias opSlice this;
403 
404 	@safe pure nothrow @nogc
405 	void opOpAssign(string op : "~")(char ch)
406 	in
407 	{
408 		assert( offset > 0 );
409 	}
410 	body
411 	{
412 		buffer[--offset] = ch;
413 	}
414 
415 
416 	@safe pure nothrow @nogc
417 	@property inout(char)[] opSlice() inout
418 	{
419 		return buffer[offset .. n];
420 	}
421 
422 
423 	@safe pure nothrow @nogc
424 	@property inout(char)* ptr() inout
425 	{
426 		return &buffer[offset];
427 	}
428 
429 
430 	@safe pure nothrow @nogc
431 	@property size_t length() const
432 	{
433 		return n - offset;
434 	}
435 }