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