aboutsummaryrefslogtreecommitdiff
path: root/lib/mlibc/options/internal/include/mlibc/strtol.hpp
blob: 3b8fca94695d5bfbf766e38d433627109148f13b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#ifndef MLIBC_STRTOL_HPP
#define MLIBC_STRTOL_HPP

#include <type_traits>
#include <ctype.h>
#include <wctype.h>
#include <limits.h>

namespace mlibc {

template<typename T> struct int_limits {};

template<>
struct int_limits<long> {
	static long max() { return LONG_MAX; }
	static long min() { return LONG_MIN; }
};

template<>
struct int_limits<unsigned long> {
	static unsigned long max() { return ULONG_MAX; }
	static unsigned long min() { return 0; }
};

template<>
struct int_limits<long long> {
	static long long max() { return LLONG_MAX; }
	static long long min() { return LLONG_MIN; }
};

template<>
struct int_limits<unsigned long long> {
	static unsigned long long max() { return ULLONG_MAX; }
	static unsigned long long min() { return 0; }
};

template<typename T> struct char_detail {};

template<>
struct char_detail<char> {
	static bool isSpace(char c) { return isspace(c); }
	static bool isDigit(char c) { return isdigit(c); }
	static bool isHexDigit(char c) { return isxdigit(c); }
	static bool isLower(char c) { return islower(c); }
	static bool isUpper(char c) { return isupper(c); }
};

template<>
struct char_detail<wchar_t> {
	static bool isSpace(wchar_t c) { return iswspace(c); }
	static bool isDigit(wchar_t c) { return iswdigit(c); }
	static bool isHexDigit(wchar_t c) { return iswxdigit(c); }
	static bool isLower(wchar_t c) { return iswlower(c); }
	static bool isUpper(wchar_t c) { return iswupper(c); }
};

template<typename Char> Char widen(char c) { return static_cast<Char>(c); }

template<typename Return, typename Char>
Return stringToInteger(const Char *__restrict nptr, Char **__restrict endptr, int baseInt) {
	using UnsignedReturn = std::make_unsigned_t<Return>;

	auto base = static_cast<Return>(baseInt);
	auto s = nptr;

	if (base < 0 || base == 1) {
		if (endptr)
			*endptr = const_cast<Char *>(nptr);
		return 0;
	}

	while (char_detail<Char>::isSpace(*s))
		s++;

	bool negative = false;
	if (*s == widen<Char>('-')) {
		negative = true;
		s++;
	} else if (*s == widen<Char>('+')) {
		s++;
	}


	bool hasOctalPrefix = s[0] == widen<Char>('0');
	bool hasHexPrefix = hasOctalPrefix && (s[1] == widen<Char>('x') || s[1] == widen<Char>('X'));

	// There's two tricky cases we need to keep in mind here:
	//   1. We should interpret "0x5" as hex 5 rather than octal 0.
	//   2. We should interpret "0x" as octal 0 (and set endptr correctly).
	// To deal with 2, we check the charcacter following the hex prefix.
	if ((base == 0 || base == 16) && hasHexPrefix && char_detail<Char>::isHexDigit(s[2])) {
		s += 2;
		base = 16;
	} else if ((base == 0 || base == 8) && hasOctalPrefix) {
		base = 8;
	} else if (base == 0) { 
		base = 10;
	}

	// Compute the range of acceptable values.
	UnsignedReturn cutoff, cutlim;
	if (std::is_unsigned_v<Return>) {
		cutoff = int_limits<Return>::max() / base;
		cutlim = int_limits<Return>::max() % base;
	} else {
		Return co = negative ? int_limits<Return>::min() : int_limits<Return>::max();
		cutlim = negative ? -(co % base) : co % base;
		co /= negative ? -base : base;
		cutoff = co;
	}

	UnsignedReturn totalValue = 0;
	bool convertedAny = false;
	bool outOfRange = false;
	for (Char c = *s; c != widen<Char>('\0'); c = *++s) {
		UnsignedReturn digitValue;
		if (char_detail<Char>::isDigit(c))
			digitValue = c - widen<Char>('0');
		else if (char_detail<Char>::isUpper(c))
			digitValue = c - widen<Char>('A') + 10;
		else if (char_detail<Char>::isLower(c))
			digitValue = c - widen<Char>('a') + 10;
		else
			break;

		if (digitValue >= static_cast<UnsignedReturn>(base))
			break;

		if (outOfRange) {
			// The value is already known to be out of range, but we need to keep
			// consuming characters until we can't (to set endptr correctly).
		} else if (totalValue > cutoff || (totalValue == cutoff && digitValue > cutlim)) {
			// The value will be out of range if we accumulate digitValue.
			outOfRange = true;
		} else {
			totalValue = (totalValue * base) + digitValue;
			convertedAny = true;
		}
	}

	if (endptr)
		*endptr = const_cast<Char *>(convertedAny ? s : nptr);

	if (outOfRange) {
		errno = ERANGE;

		if (std::is_unsigned_v<Return>) {
			return int_limits<Return>::max();
		} else {
			return negative ? int_limits<Return>::min() : int_limits<Return>::max();
		}
	}

	return negative ? -totalValue : totalValue;
}

}

#endif // MLIBC_STRTOL_HPP