numeric/str: Add constexpr int decode

This makes it possible to convert strings to numbers in a constexpr
context.

Change-Id: I051ce8600b643533cc25a8de06e15c917db9fbd7
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/stdplus/numeric/str.hpp b/include/stdplus/numeric/str.hpp
index fa5ba86..680ceb7 100644
--- a/include/stdplus/numeric/str.hpp
+++ b/include/stdplus/numeric/str.hpp
@@ -7,6 +7,7 @@
 #include <concepts>
 #include <cstdint>
 #include <limits>
+#include <stdexcept>
 #include <string>
 #include <type_traits>
 
@@ -130,6 +131,104 @@
     return uintToStr<base>(buf, std::make_unsigned_t<T>(v), min_width);
 }
 
+inline constexpr auto charTable = []() {
+    std::array<int8_t, 256> ret;
+    std::fill(ret.begin(), ret.end(), -1);
+    for (int8_t i = 0; i < 10; ++i)
+    {
+        ret[i + '0'] = i;
+    }
+    for (int8_t i = 0; i < 26; ++i)
+    {
+        ret[i + 'A'] = i + 10;
+        ret[i + 'a'] = i + 10;
+    }
+    return ret;
+}();
+
+template <uint8_t base, typename T, typename CharT>
+constexpr T strToUInt(std::basic_string_view<CharT> str)
+{
+    static_assert(std::is_unsigned_v<T>);
+    if (str.empty())
+    {
+        throw std::invalid_argument("Empty Str");
+    }
+    constexpr auto max = std::numeric_limits<T>::max();
+    T ret = 0;
+    for (auto c : str)
+    {
+        constexpr auto cmax = 1 << (sizeof(CharT) << 3);
+        if constexpr (detail::charTable.size() < cmax)
+        {
+            if (detail::charTable.size() <= c)
+            {
+                throw std::invalid_argument("Invalid numeral");
+            }
+        }
+        auto v = detail::charTable[c];
+        if (v < 0 || v >= base)
+        {
+            throw std::invalid_argument("Invalid numeral");
+        }
+        if constexpr (std::popcount(base) == 1)
+        {
+            constexpr auto shift = std::countr_zero(base);
+            constexpr auto maxshift = max >> shift;
+            if (ret > maxshift)
+            {
+                throw std::overflow_error("Integer Decode Overflow");
+            }
+            ret = (ret << shift) | v;
+        }
+        else
+        {
+            constexpr auto maxbase = max / base;
+            if (ret > maxbase)
+            {
+                throw std::overflow_error("Integer Decode Overflow");
+            }
+            ret *= base;
+            if (max - v < ret)
+            {
+                throw std::overflow_error("Integer Decode Overflow");
+            }
+            ret += v;
+        }
+    }
+    return ret;
+}
+
+template <uint8_t base, typename T, typename CharT>
+constexpr T strToUInt0(std::basic_string_view<CharT> str)
+{
+    if constexpr (base > 0)
+    {
+        return strToUInt<base, T>(str);
+    }
+    if (str.starts_with("0x"))
+    {
+        return strToUInt<16, T>(str.substr(2));
+    }
+    return strToUInt<10, T>(str);
+}
+
+template <uint8_t base, std::integral T, typename CharT>
+constexpr T strToInt(std::basic_string_view<CharT> str)
+{
+    if constexpr (std::is_unsigned_v<T>)
+    {
+        return strToUInt0<base, T>(str);
+    }
+    bool is_neg = str.starts_with('-');
+    auto ret = strToUInt0<base, std::make_unsigned_t<T>>(str.substr(is_neg));
+    if (ret > std::numeric_limits<T>::max())
+    {
+        throw std::overflow_error("Integer Decode Overflow");
+    }
+    return is_neg ? -static_cast<T>(ret) : ret;
+}
+
 } // namespace detail
 
 template <uint8_t base, std::integral T>
@@ -167,4 +266,31 @@
     using type = T;
 };
 
+template <uint8_t base, std::integral T>
+struct StrToInt
+{
+    static_assert(base <= detail::maxBase);
+
+    template <typename CharT>
+    constexpr T operator()(std::basic_string_view<CharT> str) const
+    {
+        using ptr_t =
+            std::conditional_t<std::is_signed_v<T>, intptr_t, uintptr_t>;
+        auto ret = detail::strToInt<
+            base, std::conditional_t<sizeof(T) <= sizeof(ptr_t), ptr_t, T>>(
+            str);
+        if (ret > std::numeric_limits<T>::max())
+        {
+            throw std::overflow_error("Integer Decode");
+        }
+        return ret;
+    }
+};
+
+template <std::integral T>
+struct FromStr<T> : StrToInt<0, T>
+{
+    using type = T;
+};
+
 } // namespace stdplus
diff --git a/src/numeric/str.cpp b/src/numeric/str.cpp
index 7c3ac13..81e936d 100644
--- a/src/numeric/str.cpp
+++ b/src/numeric/str.cpp
@@ -4,4 +4,6 @@
 {
 template char* uintToStr<16>(char*, uintptr_t, uint8_t) noexcept;
 template char* uintToStr<10>(char*, uintptr_t, uint8_t) noexcept;
+template uintptr_t strToUInt<16>(std::string_view);
+template uintptr_t strToUInt<10>(std::string_view);
 } // namespace stdplus::detail
diff --git a/test/numeric/str.cpp b/test/numeric/str.cpp
index 008cbe5..cedc87c 100644
--- a/test/numeric/str.cpp
+++ b/test/numeric/str.cpp
@@ -4,6 +4,8 @@
 
 #include <gtest/gtest.h>
 
+using std::literals::string_view_literals::operator""sv;
+
 namespace stdplus
 {
 
@@ -100,4 +102,95 @@
     EXPECT_TRUE(false);
 }
 
+TEST(StrToInt, Uint8_10)
+{
+    StrToInt<10, uint8_t> dec;
+    EXPECT_EQ(42, dec("42"sv));
+    EXPECT_EQ(255, dec("255"sv));
+    EXPECT_THROW(dec(""sv), std::invalid_argument);
+    EXPECT_THROW(dec("-1"sv), std::invalid_argument);
+    EXPECT_THROW(dec("a0"sv), std::invalid_argument);
+    EXPECT_THROW(dec(".0"sv), std::invalid_argument);
+    EXPECT_THROW(dec("257"sv), std::overflow_error);
+    EXPECT_THROW(dec("300"sv), std::overflow_error);
+}
+
+TEST(StrToInt, Uint8_11)
+{
+    StrToInt<11, uint8_t> dec;
+    EXPECT_EQ(112, dec("a2"sv));
+    EXPECT_EQ(255, dec("212"sv));
+    EXPECT_THROW(dec(""sv), std::invalid_argument);
+    EXPECT_THROW(dec("-1"sv), std::invalid_argument);
+    EXPECT_THROW(dec("b0"sv), std::invalid_argument);
+    EXPECT_THROW(dec(".0"sv), std::invalid_argument);
+    EXPECT_THROW(dec("213"sv), std::overflow_error);
+    EXPECT_THROW(dec("300"sv), std::overflow_error);
+}
+
+TEST(StrToInt, Uint16_16)
+{
+    StrToInt<16, uint16_t> dec;
+    EXPECT_EQ(0x42, dec("42"sv));
+    EXPECT_EQ(0xfacf, dec("facf"sv));
+    EXPECT_THROW(dec(""sv), std::invalid_argument);
+    EXPECT_THROW(dec("-1"sv), std::invalid_argument);
+    EXPECT_THROW(dec("g0"sv), std::invalid_argument);
+    EXPECT_THROW(dec(".0"sv), std::invalid_argument);
+    EXPECT_THROW(dec("10000"sv), std::overflow_error);
+}
+
+TEST(StrToInt, Uint16_8)
+{
+    StrToInt<8, uint16_t> dec;
+    EXPECT_EQ(042, dec("42"sv));
+    EXPECT_EQ(0177777, dec("177777"sv));
+    EXPECT_THROW(dec(""sv), std::invalid_argument);
+    EXPECT_THROW(dec("-1"sv), std::invalid_argument);
+    EXPECT_THROW(dec("g0"sv), std::invalid_argument);
+    EXPECT_THROW(dec(".0"sv), std::invalid_argument);
+    EXPECT_THROW(dec("277777"sv), std::overflow_error);
+}
+
+TEST(StrToInt, Int8_16)
+{
+    StrToInt<16, int8_t> dec;
+    EXPECT_EQ(-1, dec("-1"sv));
+    EXPECT_EQ(0x42, dec("42"sv));
+    EXPECT_EQ(-0x7f, dec("-7f"sv));
+    EXPECT_THROW(dec(""sv), std::invalid_argument);
+    EXPECT_THROW(dec("--1"sv), std::invalid_argument);
+    EXPECT_THROW(dec("g0"sv), std::invalid_argument);
+    EXPECT_THROW(dec(".0"sv), std::invalid_argument);
+    EXPECT_THROW(dec("ff"sv), std::overflow_error);
+    EXPECT_THROW(dec("10000"sv), std::overflow_error);
+}
+
+TEST(StrToInt, Int8_0)
+{
+    StrToInt<0, int8_t> dec;
+    EXPECT_EQ(-1, dec("-1"sv));
+    EXPECT_EQ(42, dec("42"sv));
+    EXPECT_EQ(0x42, dec("0x42"sv));
+    EXPECT_EQ(-42, dec("-42"sv));
+    EXPECT_EQ(-0x42, dec("-0x42"sv));
+    EXPECT_THROW(dec(""sv), std::invalid_argument);
+    EXPECT_THROW(dec("--1"sv), std::invalid_argument);
+    EXPECT_THROW(dec("ac"sv), std::invalid_argument);
+    EXPECT_THROW(dec(".0"sv), std::invalid_argument);
+    EXPECT_THROW(dec("0xff"sv), std::overflow_error);
+    EXPECT_THROW(dec("10000"sv), std::overflow_error);
+}
+
+TEST(StrToInt, Perf)
+{
+    GTEST_SKIP();
+    StrToInt<16, size_t> dec;
+    for (size_t i = 0; i < 100000000; ++i)
+    {
+        dec("53036893"sv);
+    }
+    EXPECT_TRUE(false);
+}
+
 } // namespace stdplus