diff options
Diffstat (limited to 'lib/mlibc/options/internal/include/mlibc/strtol.hpp')
-rw-r--r-- | lib/mlibc/options/internal/include/mlibc/strtol.hpp | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/lib/mlibc/options/internal/include/mlibc/strtol.hpp b/lib/mlibc/options/internal/include/mlibc/strtol.hpp new file mode 100644 index 0000000..3b8fca9 --- /dev/null +++ b/lib/mlibc/options/internal/include/mlibc/strtol.hpp @@ -0,0 +1,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 |