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