1 /// 2 module numparse; 3 4 import std.traits : isFloatingPoint, isIntegral, isUnsigned, isSigned, Unsigned, Signed; 5 6 /// 7 enum ParseError 8 { 9 none, /// 10 wrongSymbol, /// 11 valueLimit, /// 12 elementCount /// 13 } 14 15 pure @nogc nothrow @trusted 16 ParseError parseUintNumber(int bais, T)(ref T dst, scope const(char)[] str, uint* pow=null) 17 if (isIntegral!T && isUnsigned!T) 18 { 19 ulong result; 20 21 static immutable tbl = buildSymbolTr(bais); 22 if (str.length == 0) 23 { 24 dst = 0; 25 return ParseError.none; 26 } 27 if (str.length > 64) return ParseError.valueLimit; 28 29 int i; 30 foreach (char c; str[0..$-1]) 31 { 32 const v = tbl[c]; 33 if (v == -1) return ParseError.wrongSymbol; 34 result = (result + v) * bais; 35 i++; 36 } 37 const v = tbl[str[$-1]]; 38 if (v == -1) return ParseError.wrongSymbol; 39 result += v; 40 i++; 41 42 if (pow !is null) *pow = i; 43 44 version (checkfullstrlength) 45 { 46 static immutable ms = maxSymbols!(bais, T); 47 if (i > ms) return ParseError.valueLimit; 48 } 49 50 if (result > T.max) return ParseError.valueLimit; 51 dst = cast(T)result; 52 return ParseError.none; 53 } 54 55 unittest 56 { 57 import std : enforce, format, filter, map, array, AliasSeq; 58 59 void testSuccessParse(int bais, T)(string ostr, T need, size_t line=__LINE__) 60 { 61 T val; 62 const str = ostr.filter!"a!='_'".map!"cast(char)a".array; 63 const err = parseUintNumber!bais(val, str); 64 enforce (err == ParseError.none, 65 new Exception(format!"parse fails for '%s' with bais %d and type '%s': %s" 66 (str, bais, T.stringof, err), __FILE__, line)); 67 enforce (val == need, 68 new Exception(format!"wrong value for '%s' with bais %d and type '%s': %s (need %s)" 69 (str, bais, T.stringof, val, need), __FILE__, line)); 70 } 71 72 void testFailureParse(int bais, T)(string ostr, ParseError need, size_t line=__LINE__) 73 { 74 T val; 75 const str = ostr.filter!"a!='_'".map!"cast(char)a".array; 76 const err = parseUintNumber!bais(val, str); 77 enforce (err == need, 78 new Exception(format!"parse return wrong error for '%s' with bais %d and type '%s': %s (need %s)" 79 (str, bais, T.stringof, err, need), __FILE__, line)); 80 } 81 82 static immutable baises = [2, 8, 10, 16]; 83 alias TYPES = AliasSeq!(ubyte, ushort, uint, ulong); 84 85 static foreach (b; baises) 86 { 87 static foreach (T; TYPES) 88 { 89 testSuccessParse!(b, T)("", 0); 90 testSuccessParse!(b, T)("0", 0); 91 testSuccessParse!(b, T)("1", 1); 92 } 93 } 94 95 static foreach (T; TYPES) 96 { 97 foreach (ubyte bs; 0 .. ubyte.max) 98 { 99 testSuccessParse!(2, T)(format!"%b"(bs), bs); 100 testSuccessParse!(8, T)(format!"%o"(bs), bs); 101 testSuccessParse!(10, T)(format!"%d"(bs), bs); 102 testSuccessParse!(16, T)(format!"%x"(bs), bs); 103 104 testSuccessParse!(2, T)(format!"%08b"(bs), bs); 105 testSuccessParse!(8, T)(format!"%03o"(bs), bs); 106 testSuccessParse!(10, T)(format!"%03d"(bs), bs); 107 testSuccessParse!(16, T)(format!"%02x"(bs), bs); 108 } 109 110 foreach (ubyte obs; 1 .. ubyte.max) 111 { 112 const bs = obs << 8; 113 static if (is(T == ubyte)) 114 { 115 testFailureParse!(2, T)(format!"%b"(bs), ParseError.valueLimit); 116 testFailureParse!(8, T)(format!"%o"(bs), ParseError.valueLimit); 117 testFailureParse!(10, T)(format!"%d"(bs), ParseError.valueLimit); 118 testFailureParse!(16, T)(format!"%x"(bs), ParseError.valueLimit); 119 } 120 else 121 { 122 testSuccessParse!(2, T)(format!"%b"(bs), bs); 123 testSuccessParse!(8, T)(format!"%o"(bs), bs); 124 testSuccessParse!(10, T)(format!"%d"(bs), bs); 125 testSuccessParse!(16, T)(format!"%x"(bs), bs); 126 127 testSuccessParse!(2, T)(format!"%016b"(bs), bs); 128 testSuccessParse!(8, T)(format!"%06o"(bs), bs); 129 testSuccessParse!(10, T)(format!"%05d"(bs), bs); 130 testSuccessParse!(16, T)(format!"%04x"(bs), bs); 131 } 132 } 133 } 134 135 testSuccessParse!(2, ubyte)("1", 1); 136 testSuccessParse!(2, ubyte)("0001", 1); 137 testSuccessParse!(2, ubyte)("0000_0001", 1); 138 139 version (checkfullstrlength) 140 { 141 pragma(msg, "checkfullstrlength"); 142 testFailureParse!(2, ubyte)("0_0000_0000", ParseError.valueLimit); 143 testFailureParse!(2, ubyte)("0_0000_0001", ParseError.valueLimit); 144 } 145 146 testSuccessParse!(2, ushort)("0_0000_0000", 0); 147 testSuccessParse!(2, ushort)("0_0000_0001", 1); 148 149 testSuccessParse!(2, uint)("1011_1001_1010_1100_0110_1010_1010_0011", 150 0b1011_1001_1010_1100_0110_1010_1010_0011); 151 152 testSuccessParse!(2, ushort)("11111111", ubyte.max); 153 testSuccessParse!(2, ushort)("1111111111111111", ushort.max); 154 testSuccessParse!(2, uint)("11111111111111111111111111111111", uint.max); 155 testSuccessParse!(2, ulong)("1111111111111111111111111111111111111111111111111111111111111111", ulong.max); 156 157 testSuccessParse!(10, uint)("0145023", 145023); 158 testSuccessParse!(16, uint)("abc3_af99", 0xabc3_af99); 159 } 160 161 unittest 162 { 163 uint val; 164 uint pow; 165 assert (parseUintNumber!10(val, "1", &pow) == ParseError.none); 166 assert (val == 1); 167 assert (pow == 1); 168 assert (parseUintNumber!10(val, "10", &pow) == ParseError.none); 169 assert (val == 10); 170 assert (pow == 2); 171 assert (parseUintNumber!10(val, "01", &pow) == ParseError.none); 172 assert (val == 1); 173 assert (pow == 2); 174 assert (parseUintNumber!10(val, "001", &pow) == ParseError.none); 175 assert (val == 1); 176 assert (pow == 3); 177 } 178 179 pure @nogc nothrow @trusted 180 ParseError parseIntNumber(int bais, T)(ref T dst, scope const(char)[] str, uint* pow=null) 181 if (isIntegral!T && isSigned!T) 182 { 183 if (str.length == 0) 184 { 185 dst = 0; 186 return ParseError.none; 187 } 188 189 size_t s = 0; 190 T k = 1; 191 if (str[0] == '-') 192 { 193 k = -1; 194 s = 1; 195 } 196 else if (str[0] == '+') 197 { 198 s = 1; 199 } 200 Unsigned!T result; 201 const r = parseUintNumber!bais(result, str[s..$], pow); 202 if (r != ParseError.none) return r; 203 dst = (cast(T)result) * k; 204 return ParseError.none; 205 } 206 207 unittest 208 { 209 int val; 210 assert (parseIntNumber!10(val, "") == ParseError.none); 211 assert (val == 0); 212 assert (parseIntNumber!10(val, "0") == ParseError.none); 213 assert (val == 0); 214 assert (parseIntNumber!10(val, "1") == ParseError.none); 215 assert (val == 1); 216 assert (parseIntNumber!10(val, "10") == ParseError.none); 217 assert (val == 10); 218 assert (parseIntNumber!10(val, "-10") == ParseError.none); 219 assert (val == -10); 220 assert (parseIntNumber!10(val, "+10") == ParseError.none); 221 assert (val == 10); 222 } 223 224 pure @nogc nothrow @trusted 225 ParseError parseSimpleFloatNumber(int bais, T)(ref T dst, scope const(char)[] str, char sep='.') 226 if (isFloatingPoint!T) 227 { 228 if (str.length == 0) 229 { 230 dst = 0; 231 return ParseError.none; 232 } 233 234 size_t s = 0; 235 T k = 1; 236 if (str[0] == '-') 237 { 238 k = -1; 239 s = 1; 240 } 241 else if (str[0] == '+') 242 { 243 s = 1; 244 } 245 246 size_t sp; 247 foreach (char c; str) 248 { 249 if (c == sep) break; 250 sp++; 251 } 252 253 ulong c; 254 if (sp > 0) 255 { 256 const r = parseUintNumber!bais(c, str[s..sp]); 257 if (r != ParseError.none) return r; 258 } 259 260 sp++; 261 262 T frac = 0; 263 if (sp < str.length) 264 { 265 uint pow; 266 ulong f; 267 auto r2 = parseUintNumber!bais(f, str[sp..$], &pow); 268 if (r2 != ParseError.none) return r2; 269 T div = 1; 270 foreach (i; 0 .. pow) div *= bais; 271 frac = f / div; 272 } 273 dst = (c + frac) * k; 274 return ParseError.none; 275 } 276 277 unittest 278 { 279 import std : enforce, format, filter, map, array, AliasSeq, abs; 280 281 void testSuccessParse(int bais, T)(string str, T need, size_t line=__LINE__) 282 { 283 T val; 284 const err = parseSimpleFloatNumber!bais(val, str); 285 enforce (err == ParseError.none, 286 new Exception(format!"parse fails for '%s' with bais %d and type '%s': %s" 287 (str, bais, T.stringof, err), __FILE__, line)); 288 enforce (abs(val - need) < T.epsilon * 8, 289 new Exception(format!"wrong value for '%s' with bais %d and type '%s': %s (need %s)" 290 (str, bais, T.stringof, val, need), __FILE__, line)); 291 } 292 293 void testFailureParse(int bais, T)(string str, ParseError need, size_t line=__LINE__) 294 { 295 T val; 296 const err = parseSimpleFloatNumber!bais(val, str); 297 enforce (err == need, 298 new Exception(format!"parse return wrong error for '%s' with bais %d and type '%s': %s (need %s)" 299 (str, bais, T.stringof, err, need), __FILE__, line)); 300 } 301 302 testSuccessParse!(10, float)("", 0.0); 303 testSuccessParse!(10, float)(".", 0.0); 304 testSuccessParse!(10, float)("0", 0.0); 305 testSuccessParse!(10, float)(".0", 0.0); 306 testSuccessParse!(10, float)("0.", 0.0); 307 testSuccessParse!(10, float)("0.0", 0.0); 308 309 testSuccessParse!(10, float)("-", 0.0); 310 testSuccessParse!(10, float)("-.", 0.0); 311 testSuccessParse!(10, float)("-0", 0.0); 312 testSuccessParse!(10, float)("-.0", 0.0); 313 testSuccessParse!(10, float)("-0.", 0.0); 314 testSuccessParse!(10, float)("-0.0", 0.0); 315 316 testSuccessParse!(10, float)("2", 2.0); 317 testSuccessParse!(10, float)("2.", 2.0); 318 testSuccessParse!(10, float)("2.0", 2.0); 319 testSuccessParse!(10, float)("20", 20.0); 320 testSuccessParse!(10, float)("2.2", 2.2); 321 testSuccessParse!(10, float)(".2", 0.2); 322 testSuccessParse!(10, float)(".002", 0.002); 323 testSuccessParse!(10, float)("30.002", 30.002); 324 325 testSuccessParse!(10, float)("-2", -2.0); 326 testSuccessParse!(10, float)("-2.", -2.0); 327 testSuccessParse!(10, float)("-2.0", -2.0); 328 testSuccessParse!(10, float)("-20", -20.0); 329 testSuccessParse!(10, float)("-2.2", -2.2); 330 testSuccessParse!(10, float)("-.2", -0.2); 331 testSuccessParse!(10, float)("-.002", -0.002); 332 testSuccessParse!(10, float)("-30.002", -30.002); 333 334 testSuccessParse!(10, float)("+2", 2.0); 335 testSuccessParse!(10, float)("+2.", 2.0); 336 testSuccessParse!(10, float)("+2.0", 2.0); 337 testSuccessParse!(10, float)("+20", 20.0); 338 testSuccessParse!(10, float)("+2.2", 2.2); 339 testSuccessParse!(10, float)("+.2", 0.2); 340 testSuccessParse!(10, float)("+.002", 0.002); 341 testSuccessParse!(10, float)("+30.002", 30.002); 342 343 testFailureParse!(10, float)("2+", ParseError.wrongSymbol); 344 testFailureParse!(10, float)("2.+", ParseError.wrongSymbol); 345 testFailureParse!(10, float)("2.+0", ParseError.wrongSymbol); 346 testFailureParse!(10, float)("2.0+", ParseError.wrongSymbol); 347 testFailureParse!(10, float)("2.+2", ParseError.wrongSymbol); 348 testFailureParse!(10, float)(".+2", ParseError.wrongSymbol); 349 testFailureParse!(10, float)(".+002", ParseError.wrongSymbol); 350 testFailureParse!(10, float)("30+.002", ParseError.wrongSymbol); 351 352 testFailureParse!(10, float)("2-", ParseError.wrongSymbol); 353 testFailureParse!(10, float)("2.-", ParseError.wrongSymbol); 354 testFailureParse!(10, float)("2.-0", ParseError.wrongSymbol); 355 testFailureParse!(10, float)("2.0-", ParseError.wrongSymbol); 356 testFailureParse!(10, float)("2.-2", ParseError.wrongSymbol); 357 testFailureParse!(10, float)(".-2", ParseError.wrongSymbol); 358 testFailureParse!(10, float)(".-002", ParseError.wrongSymbol); 359 testFailureParse!(10, float)("30-.002", ParseError.wrongSymbol); 360 } 361 362 pure @nogc nothrow @trusted 363 ParseError parseUintNumbers(int bais, T)(T[] dst, scope const(char)[] str, char splt) 364 { 365 if (str.length == 0) return ParseError.none; 366 367 size_t cnt; 368 foreach (char c; str) cnt += c == splt; 369 if (cnt + 1 != dst.length) return ParseError.elementCount; 370 371 size_t s, i; 372 foreach (e, char c; str) 373 { 374 if (c == splt) 375 { 376 if (s < e) 377 { 378 const r = parseUintNumber!bais(dst[i], str[s..e]); 379 if (r != ParseError.none) return r; 380 } 381 i++; 382 383 s = e+1; 384 } 385 } 386 if (s < str.length) 387 { 388 const r = parseUintNumber!bais(dst[i], str[s..$]); 389 if (r != ParseError.none) return r; 390 } 391 392 return ParseError.none; 393 } 394 395 unittest 396 { 397 import std : enforce, format, filter, map, array, AliasSeq; 398 399 void testSuccessParse(int bais, T)(string str, char splt, T[] need, string file=__FILE__, size_t line=__LINE__) 400 { 401 T[] val; 402 val.length = need.length; 403 const err = parseUintNumbers!bais(val, str, splt); 404 enforce (err == ParseError.none, 405 new Exception(format!"parse fails for '%s' with bais %d and type '%s': %s" 406 (str, bais, T.stringof, err), file, line)); 407 enforce (val == need, 408 new Exception(format!"wrong value for '%s' with bais %d and type '%s': %s (need %s)" 409 (str, bais, T.stringof, val, need), file, line)); 410 } 411 412 testSuccessParse!(10, ubyte)("...", '.', [0, 0, 0, 0]); 413 testSuccessParse!(10, ubyte)("...5", '.', [0, 0, 0, 5]); 414 testSuccessParse!(10, ubyte)("..5.", '.', [0, 0, 5, 0]); 415 testSuccessParse!(10, ubyte)(".5..", '.', [0, 5, 0, 0]); 416 testSuccessParse!(10, ubyte)("5...", '.', [5, 0, 0, 0]); 417 testSuccessParse!(10, ubyte)("5.5..", '.', [5, 5, 0, 0]); 418 testSuccessParse!(10, ubyte)("5..5.", '.', [5, 0, 5, 0]); 419 testSuccessParse!(10, ubyte)("5...5", '.', [5, 0, 0, 5]); 420 testSuccessParse!(10, ubyte)("5..5.5", '.', [5, 0, 5, 5]); 421 testSuccessParse!(10, ubyte)("5.5..5", '.', [5, 5, 0, 5]); 422 testSuccessParse!(10, ubyte)("5.5.5.5", '.', [5, 5, 5, 5]); 423 testSuccessParse!(10, ubyte)("123.0.10.5", '.', [123, 0, 10, 5]); 424 425 //testFailureParse!(10, ubyte)("...", '.', [0, 0, 0, 0]); 426 } 427 428 private: 429 430 size_t maxSymbols(int bais, T)() 431 { 432 enum TS = T.sizeof; 433 enum TSb = TS * 8; 434 import std.math; 435 return cast(size_t)ceil(TSb / log2(bais)); 436 } 437 438 unittest 439 { 440 static assert(maxSymbols!(2, ubyte) == 8); 441 static assert(maxSymbols!(2, ushort) == 16); 442 static assert(maxSymbols!(2, uint) == 32); 443 static assert(maxSymbols!(2, ulong) == 64); 444 445 static assert(maxSymbols!(8, ubyte) == 3); 446 static assert(maxSymbols!(8, ushort) == 6); 447 static assert(maxSymbols!(8, uint) == 11); 448 static assert(maxSymbols!(8, ulong) == 22); 449 450 static assert(maxSymbols!(10, ubyte) == 3); 451 static assert(maxSymbols!(10, ushort) == 5); 452 static assert(maxSymbols!(10, uint) == 10); 453 static assert(maxSymbols!(10, ulong) == 20); 454 455 static assert(maxSymbols!(16, ubyte) == 2); 456 static assert(maxSymbols!(16, ushort) == 4); 457 static assert(maxSymbols!(16, uint) == 8); 458 static assert(maxSymbols!(16, ulong) == 16); 459 } 460 461 pure @nogc nothrow @safe 462 byte[ubyte.max] buildSymbolTr(int bais) 463 { 464 if (bais < 2 || bais > 16) assert(0, "unsupported bais"); 465 import std.uni : toLower, toUpper; 466 typeof(return) r; 467 static immutable char[] sym = "0123456789abcdef"; 468 r[] = -1; 469 foreach (i; 0 .. bais) 470 { 471 r[cast(ubyte)(sym[i].toLower)] = cast(byte)i; 472 r[cast(ubyte)(sym[i].toUpper)] = cast(byte)i; 473 } 474 return r; 475 }