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 }