1 /++ Conversion that use result types instead of exception handling.
2  +/
3 module nxt.conv;
4 
5 import nxt.result : Result;
6 
7 @safe:
8 
9 /++ Try to parse `s` as a `T`. +/
10 Result!(T) tryParse(T)(scope const(char)[] s) pure nothrow @nogc
11 if (__traits(isArithmetic, T) && __traits(isIntegral, T)) {
12 	alias R = typeof(return);
13 
14 	// accumulator type
15 	static if (__traits(isUnsigned, T))
16 		alias A = ulong;
17 	else
18 		alias A = long;
19 
20 	if (!s.length) // empty
21 		return R.invalid;
22 
23 	// strip optional leading {plus|minus} sign
24 	bool minus;
25 	if (s.length) {
26 		if (s[0] == '-')
27 			minus = true;
28 		if (s[0] == '+' || s[0] == '-')
29 			s = s[1 .. $];
30 	}
31 
32 	// accumulate
33 	A curr = 0; // current accumulated value
34     foreach (const c; s) {
35         if (c < '0' || c > '9') // non-digit
36 			return R.invalid;
37         A prev = curr;
38         curr = 10 * curr + (c - '0'); // accumulate
39         if (curr < prev) // overflow
40 			return R.invalid;
41     }
42 
43 	// range check
44 	assert(T.min <= curr);
45 	assert(curr <= T.max);
46 
47     return R(cast(T)curr);
48 }
49 
50 @safe pure nothrow @nogc unittest {
51 	foreach (T; IntegralTypes) {
52 		assert(!"".tryParse!T.isValid); // empty
53 		assert(!"_".tryParse!T.isValid); // non-digit
54 		assert(*"+0".tryParse!T == 0);
55 		assert(!*"+0".tryParse!T);
56 		assert(*"+1".tryParse!T == 1);
57 		assert(*"-0".tryParse!T == 0);
58 		assert(*"0".tryParse!T == 0);
59 		assert(*"1".tryParse!T == 1);
60 		assert(*"2".tryParse!T == 2);
61 		assert(*"10".tryParse!T == 10);
62 		assert(*"11".tryParse!T == 11);
63 	}
64 	// unsigned min
65 	foreach (T; UnsignedTypes) {
66 		assert(*"0".tryParse!T == T.min);
67 		assert(*"+0".tryParse!T == T.min);
68 	}
69 	// unsigned max
70 	assert(*"255".tryParse!ubyte == 255);
71 	assert(*"65535".tryParse!ushort == 65535);
72 	assert(*"4294967295".tryParse!uint == 4294967295);
73 	assert(*"18446744073709551615".tryParse!ulong == ulong.max);
74 	// signed max
75 	assert(*"9223372036854775807".tryParse!long == long.max);
76 	// overflow
77 	assert(!"18446744073709551616".tryParse!ulong);
78 }
79 
80 version (unittest) {
81 	import std.meta : AliasSeq;
82 	alias UnsignedTypes = AliasSeq!(ubyte, ushort, uint, ulong);
83 	alias SignedTypes = AliasSeq!(byte, short, int, long);
84 	alias IntegralTypes = AliasSeq!(ubyte, ushort, uint, ulong, byte, short, int, long);
85 }