1 /// Module to replace std.conv 'to' function with similar but @nogc
2 /// Function in this module use TLS buffer to store string result, so returned strings are valid only to next usage of X->string conversion functions
3 module mutils.conv;
4 
5 import std.traits;
6 import std.meta: NoDuplicates;
7 
8 extern(C) int sscanf(scope const char* s, scope const char* format, ...) nothrow @nogc;
9 extern(C) int snprintf (scope char * s, size_t n, scope const char * format, ... ) nothrow @nogc;
10 
11 auto min(A, B)(A a, B b){
12 	return (a<b)?a:b;
13 }
14 
15 static char[1024] gTmpStrBuff;
16 
17 /// Converts variable from to Type TO
18 /// strings are stored in default global buffer
19 TO to(TO, FROM)(auto ref const FROM from){
20 	return to!(TO, FROM)(from, gTmpStrBuff);
21 }
22 
23 /// Converts variable from to Type TO
24 /// strings are stored in buff buffer
25 TO to(TO, FROM)(auto ref const FROM from, char[] buff){
26 	static if( is(TO==FROM) ){
27 		return from;
28 	}else static if( is(TO==string) ){
29 		static if( is(FROM==enum)){
30 			return enum2str(from, buff);
31 		}else static if( isSIMDVector!FROM ){
32 			return slice2str(from.array, buff);
33 		}else static if( worksWithStr2Num!FROM ){
34 			return num2str(from, buff);
35 		}else static if( is(FROM==struct)){
36 			return struct2str(from, buff);
37 		}else static if( isDynamicArray!(FROM) ){
38 			return slice2str(from, buff);
39 		}else static if( isStaticArray!(FROM) ){
40 			return slice2str(from[], buff);
41 		}else static if( isDelegate!(FROM) || isFunctionPointer!FROM){
42 			return del2str(from, buff);
43 		}else static if( is(FROM==class)){
44 			return num2str(cast(void*)from, buff);
45 		}else{
46 			static assert(0, "Type conversion not supported");
47 		}
48 	}else static if( is(FROM==string) ){
49 		static if( is(TO==enum)){
50 			return str2enum!(TO)(from);
51 		}else static if( worksWithStr2Num!TO ){
52 			return str2num!(TO)(from);
53 		}else static if( is(TO==struct)){
54 			return str2num!(TO)(from);
55 		}else{
56 			static assert(0, "Type conversion not supported");
57 		}
58 	}
59 
60 }
61 
62 nothrow @nogc unittest{
63 	// Convert to same value
64 	assert(10.to!int==10);
65 	// Convert numbers
66 	assert("10".to!ubyte==10);
67 	assert(10.to!string=="10");
68 
69 	// Convert enums
70 	assert((TestEnum.a).to!string=="a");
71 	assert("a".to!TestEnum==TestEnum.a);
72 
73 	// Convert structs
74 	TestStructA testStruct="TestStructA(10, 10)".str2struct!TestStructA;
75 	assert(testStruct.a==10);
76 	assert(testStruct.b==10);
77 	assert(testStruct.to!string=="TestStructA(10, 10)");
78 }
79 
80 ///////////////////////  Convert numbers
81 
82 /// Converts number of type NUM to string and stores it in buff
83 /// Internally uses snprintf, string might be cut down to fit in buffer
84 /// To check for buffer overflow you might compare length of buff and returned string, if they are equal there might be not enought space in buffer
85 /// NULL char is always added at the end of the string
86 string num2str(FROM)(FROM from, char[] buff){
87 	static assert( worksWithStr2Num!FROM, "num2str converts only numeric or pointer type to string");
88 	string sp=getSpecifier!(FROM);
89 	char[5] format;
90 	format[0]='%';
91 	foreach(i, c; sp)format.ptr[1+i]=c;
92 	format.ptr[1+sp.length]='\0';
93 	int takesCharsNum=snprintf(buff.ptr, buff.length, format.ptr, from);
94 	if(takesCharsNum<buff.length){
95 		return cast(string)buff[0..takesCharsNum];
96 	}else{
97 		return cast(string)buff;
98 	}
99 }
100 
101 nothrow @nogc unittest{
102 	char[4] buff;
103 	assert(num2str(10, gTmpStrBuff[])=="10");
104 	assert(num2str(-10, gTmpStrBuff[])=="-10");
105 	assert(num2str(123456789, buff[])=="123\0");
106 }
107 
108 /// Converts string to numeric type NUM
109 /// If string is malformed NUM.init is returned
110 NUM str2num(NUM)(string from){
111 	static assert( worksWithStr2Num!NUM, "str2num converts string to numeric or pointer type");
112 	if(from.length==0){
113 		return NUM.init;
114 	}
115 	NUM ret=NUM.init;
116 	string sp=getSpecifier!(NUM);
117 	char[32] format;
118 	format[]='K';
119 	format[0]='%';
120 	int takesCharsNum=snprintf(format.ptr+1, format.length, "%d", cast(int)from.length);
121 	foreach(i, c; sp)format.ptr[1+takesCharsNum+i]=c;
122 	format.ptr[1+takesCharsNum+sp.length]='\0';
123 	sscanf(from.ptr, format.ptr, &ret);
124 	return ret;
125 }
126 
127 nothrow @nogc unittest{
128 	char[18] noEnd="123456789123456789";// Test without c string ending (\0)
129 	string empty;
130 	assert(empty.str2num!ubyte==0);
131 	assert("".str2num!ubyte==0);
132 	assert("asdaf".str2num!ubyte==0);
133 	assert(str2num!int(cast(string)noEnd[0..2])==12);
134 
135 
136 	assert("10".str2num!ubyte==10);
137 	assert("10".str2num!ushort==10);
138 	assert("+10".str2num!uint==10);
139 	assert("+10".str2num!ulong==10);
140 	
141 	assert("-10".str2num!byte==-10);
142 	assert("-10".str2num!short==-10);
143 	assert("-10".str2num!int==-10);
144 	assert("-10".str2num!long==-10);
145 }
146 
147 ///////////////////////  Convert enums
148 
149 /// Converts enum to string
150 /// If wrong enum value is specified "WrongEnum" string is returned
151 string enum2str(T)(auto ref const T en, char[] buff){
152 	static assert( is(T==enum) , "T must be an enum");
153 	switch(en){
154 		foreach (i, e; NoDuplicates!(EnumMembers!T) ){
155 			case e:
156 			enum name = __traits(allMembers, T)[i];
157 			foreach(k,char c; name){
158 				buff[k]=c;
159 			}
160 			return cast(string)buff[0..name.length];
161 		}
162 		default:
163 			return "WrongEnum";
164 			
165 	}
166 }
167 
168 nothrow @nogc unittest{
169 	assert(enum2str(TestEnum.a, gTmpStrBuff)=="a");
170 	assert(enum2str(TestEnum.b, gTmpStrBuff)=="b");
171 	assert(enum2str(cast(TestEnum)123, gTmpStrBuff)=="WrongEnum");
172 }
173 
174 /// Converts string to enum
175 /// If wrong string is specified max enum base type is returned ex. for: enum:ubyte E{} will return 255
176 T str2enum(T)(string str){
177 	static assert( is(T==enum) , "T must be an enum");
178 	T en;
179 	switch(str){
180 		foreach (i, e; NoDuplicates!(EnumMembers!T) ){
181 			enum name = __traits(allMembers, T)[i];
182 			case name:
183 			return e;
184 		}
185 		default:
186 			return cast(TestEnum)(OriginalType!T).max;// Probably invalid enum
187 			
188 	}
189 }
190 
191 nothrow @nogc unittest{
192 	assert(str2enum!(TestEnum)("a")==TestEnum.a);
193 	assert(str2enum!(TestEnum)("b")==TestEnum.b);
194 	assert(str2enum!(TestEnum)("ttt")==byte.max);
195 }
196 
197 ///////////////////////  Convert slices
198 
199 /// Converts slice to string
200 /// Uses to!(string)(el, buff) to convert inner elements
201 /// If buff.length<=5 null is returned
202 /// If there is not enought space in the buffer, function converts as much as it coud with string "...]" at the end 
203 string slice2str(T)(auto ref const T slice, char[] buff){
204 	alias EL=Unqual!(ForeachType!T);
205 
206 	if(buff.length<=5){
207 		return null;
208 	}
209 
210 	static if( is(EL==char) ){
211 		buff[$-4..$]="...]";
212 		size_t lengthToCopy=min(slice.length, buff.length-2);
213 		buff[0]='"';
214 		buff[1+lengthToCopy]='"';
215 		buff[1..1+lengthToCopy]=slice[0..lengthToCopy];
216 		return cast(string)buff[0..lengthToCopy+2];
217 	}else{
218 		buff[0]='[';
219 
220 		char[] buffSlice=buff[1..$];
221 		foreach(ref el; slice){
222 			string elStr=to!(string)(el, buffSlice);
223 			if(elStr.length+2>=buffSlice.length){
224 				buff[$-4..$]="...]";
225 				buffSlice=null;
226 				break;
227 			}
228 			buffSlice[elStr.length]=',';
229 			buffSlice[elStr.length+1]=' ';
230 			buffSlice=buffSlice[elStr.length+2..$];
231 		}
232 		if(buffSlice.length==0){
233 			return cast(string)buff;
234 		}
235 
236 		size_t size=buff.length-buffSlice.length;
237 		buff[size-2]=']';
238 		return cast(string)buff[0..size-1];
239 	}
240 }
241 
242 
243 nothrow @nogc unittest{
244 	char[10] bb;
245 	TestStructA[9] sl;
246 	int[9] ints=[1,2,3,4,5,6,7,8,9];
247 	assert(slice2str(ints[], gTmpStrBuff)=="[1, 2, 3, 4, 5, 6, 7, 8, 9]");
248 	assert(slice2str(sl[], bb[])=="[TestS...]");
249 }
250 
251 ///////////////////////  Convert delegates and functions
252 ///
253 /// Converts delegate or function to string
254 string del2str(FROM)(FROM from, char[] buff){
255 	static assert( isDelegate!FROM || isFunctionPointer!FROM, "del2str converts only delegates and functions to string");
256 	static if(isDelegate!FROM){
257 		int takesCharsNum=snprintf(buff.ptr, buff.length, "delegate(obj: %p, func: %p)", from.ptr, from.funcptr);
258 	}else{
259 		int takesCharsNum=snprintf(buff.ptr, buff.length, "function(func: %p)", cast(void*)from);
260 	}
261 	if(takesCharsNum<buff.length){
262 		return cast(string)buff[0..takesCharsNum];
263 	}else{
264 		return cast(string)buff;
265 	}
266 }
267 
268 nothrow @nogc unittest{
269 	static void func(){}
270 	static void del(){}
271 	assert(del2str(&func, gTmpStrBuff));
272 	assert(del2str(&del, gTmpStrBuff));
273 }
274 
275 ///////////////////////  Convert structs
276 ///// Enums are treated as numbers
277 
278 private string getFormatString(T)(){
279 	string str;
280 
281 	static if( is(T==struct) ){
282 		str~=T.stringof~"(";
283 		alias FieldsTypes=Fields!T;
284 		foreach (i, Type; FieldsTypes) {
285 			str~=getFormatString!Type;
286 			if(i!=FieldsTypes.length-1){
287 				str~=", ";
288 			}
289 		}
290 		str~=")";
291 	}else static if( worksWithStr2Num!T ){
292 		str~="%"~getSpecifier!T;		
293 	}
294 
295 	return str;
296 }
297 
298 nothrow @nogc unittest{
299 	enum string format=getFormatString!(TestStructB);
300 	assert(format=="TestStructB(TestStructA(%d, %hhu), %d, %lld, %hhd)");
301 }
302 
303 private string[] getFullMembersNames(T,string beforeName)(string[] members){	
304 	static if( is(T==struct) ){
305 		alias FieldsTypes=Fields!T;
306 		alias FieldsNames=FieldNameTuple!T;
307 		foreach (i, Type; FieldsTypes) {
308 			enum string varName =FieldsNames[i];
309 			enum string fullName =beforeName~"."~varName;
310 			static if( is(Type==struct) ){
311 				members=getFullMembersNames!(Type, fullName)( members);
312 			}else static if( worksWithStr2Num!Type ){
313 				members~=fullName;
314 			}
315 		}
316 	}	
317 	return members;
318 }
319 
320 nothrow @nogc unittest{
321 	enum string[] fullMembersNames=getFullMembersNames!(TestStructA, "s")([]);
322 	assert(fullMembersNames==["s.a", "s.b"]);
323 }
324 
325 private string generate_snprintf_call(string returnValueName, string bufferName, string formatSpecifier, string[] fullMembers){	
326 	import std.format;
327 	string str;
328 	str~=format("%s=snprintf(%s.ptr, %s.length, \"%s\"",returnValueName, bufferName, bufferName, formatSpecifier);
329 	foreach(memb; fullMembers){
330 		str~=", "~memb;
331 	}
332 	str~=");";
333 	return str;
334 }
335 
336 nothrow @nogc unittest{
337 	enum string call=generate_snprintf_call("ret", "buff", "%d", ["s.a"]);
338 	assert(call=="ret=snprintf(buff.ptr, buff.length, \"%d\", s.a);");
339 }
340 
341 private string generate_sscanf_call( string stringName, string formatSpecifier, string[] fullMembers){	
342 	import std.format;
343 	string str;
344 	str~=format("sscanf(%s.ptr, \"%s\"", stringName, formatSpecifier);
345 	foreach(memb; fullMembers){
346 		str~=", &"~memb;
347 	}
348 	str~=");";
349 	return str;
350 }
351 
352 nothrow @nogc unittest{
353 	enum string call=generate_sscanf_call("str",  "%d", ["s.a"]);
354 	assert(call=="sscanf(str.ptr, \"%d\", &s.a);");
355 }
356 
357 /// Converts structs to strings
358 /// Function is not using to!string so inner elements might be displayed differently ex. enums (they are displayeed as numbers)
359 /// Elements which cannot be converted are skipped
360 string struct2str(T)(auto ref const T s, char[] buff){
361 	static assert( is(T==struct) , "T must be a struct");
362 
363 	enum string format=getFormatString!T;
364 	enum string[] fullMembersNames=getFullMembersNames!(T, "s")([]);
365 
366 	int takesCharsNum;
367 	mixin( generate_snprintf_call("takesCharsNum", "buff", format, fullMembersNames) );
368 	return cast(string)buff[0..min(takesCharsNum, buff.length)];
369 }
370 
371 nothrow @nogc unittest{
372 	TestStructB test=TestStructB(2);
373 	assert(struct2str(test, gTmpStrBuff)=="TestStructB(TestStructA(1, 255), 2, 9223372036854775807, 50)");
374 }
375 /// Converts string to struct
376 /// string format is very strict, returns 0 initialized variable if string is bad
377 /// Works like struct2str but opposite
378 T str2struct(T)(string str){
379 	static assert( is(T==struct) , "T must be a struct");
380 
381 	union ZeroInit{// Init for @disable this() structs
382 		mixin("align(T.alignof) ubyte[T.sizeof] zeros;");// Workaround for IDE parser error
383 		T s=void;
384 	}
385 
386 	ZeroInit var;
387 	if(str[$-1]!=')'){
388 		return var.s;
389 	}
390 
391 	enum string format=getFormatString!T;
392 	enum string[] fullMembersNames=getFullMembersNames!(T, "var.s")([]);
393 	enum string sscanf_call=generate_sscanf_call("str", format, fullMembersNames);
394 	mixin(sscanf_call);
395 	return var.s;
396 }
397 
398 nothrow @nogc unittest{
399 	string loadFrom="TestStructB(TestStructA(1, 255), 2, 9223372036854775807, 100)";
400 	TestStructB test=str2struct!(TestStructB)(loadFrom);
401 	assert(test.a==2);
402 	assert(test.b==9223372036854775807);
403 	assert(test.en==TestEnum.b);
404 	assert(test.c.a==1);
405 	assert(test.c.b==255);
406 }
407 
408 
409 private bool worksWithStr2Num(T)(){
410 	return !isSIMDVector!(T) && (isNumeric!T || isPointer!T || is(Unqual!T==char));
411 }
412 
413 unittest{
414 	import core.simd: ushort8;
415 	static assert(worksWithStr2Num!(int));
416 	static assert(worksWithStr2Num!(void*));
417 	static assert(worksWithStr2Num!(double));
418 	static assert(!worksWithStr2Num!(ushort8));
419 }
420 
421 string getSpecifier(TTT)(){
422 	static if( is(TTT==enum) ){
423 		alias T=OriginalType!TTT;
424 	}else{
425 		alias T=Unqual!TTT;
426 	}
427 	static if( is(T==float) ) return "g";
428 	else static if( is(T==double) ) return "lg";
429 	else static if( is(T==real) ) return "Lg";
430 	else static if( is(T==char) ) return "c";
431 	else static if( is(T==byte) ) return "hhd";
432 	else static if( is(T==ubyte) ) return "hhu";
433 	else static if( is(T==short) ) return "hd";
434 	else static if( is(T==ushort) ) return "hu";
435 	else static if( is(T==int) ) return "d";
436 	else static if( is(T==uint) ) return "u";
437 	else static if( is(T==long) ) return "lld";
438 	else static if( is(T==ulong) ) return "llu";
439 	else static if( isPointer!T ) return "p";
440 	else static assert(0, "Type conversion not supported");
441 }
442 
443 
444 // Used for tests
445 
446 private enum TestEnum:byte{
447 	a=50,
448 	b=100,
449 }
450 
451 
452 private struct TestStructA{
453 	int a=1;
454 	ubyte b=ubyte.max;
455 }
456 
457 private struct TestStructB{
458 	nothrow @nogc pure:
459 	@disable this();
460 	
461 	this(int a){
462 		this.a=a;
463 	}
464 	TestStructA c;
465 	int a=3;
466 	long b=long.max;
467 	TestEnum en;
468 }
469