str/conv: Add basic functions

We need some basic templates for formatting into a buffer or fmtlib to
make other conversions straightforward.

Change-Id: I8c13175394f6b4fd4a55edf8d653e98a1562cc64
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/meson.build b/include/meson.build
index 5909aa0..979478e 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -19,6 +19,7 @@
   'stdplus/str/buf.hpp',
   'stdplus/str/cat.hpp',
   'stdplus/str/cexpr.hpp',
+  'stdplus/str/conv.hpp',
   'stdplus/str/maps.hpp',
   'stdplus/util/cexec.hpp',
   'stdplus/util/string.hpp',
diff --git a/include/stdplus/str/conv.hpp b/include/stdplus/str/conv.hpp
new file mode 100644
index 0000000..cbf79d3
--- /dev/null
+++ b/include/stdplus/str/conv.hpp
@@ -0,0 +1,157 @@
+#pragma once
+#include <fmt/core.h>
+
+#include <stdplus/str/buf.hpp>
+
+#include <array>
+#include <string>
+#include <string_view>
+#include <type_traits>
+
+namespace stdplus
+{
+
+template <typename T>
+struct ToStr;
+
+template <typename T>
+struct FromStr;
+
+template <typename T>
+constexpr T fromStr(const auto& str)
+{
+    return FromStr<T>{}(
+        std::basic_string_view<std::remove_cvref_t<decltype(*std::begin(str))>>{
+            str});
+}
+
+namespace detail
+{
+
+template <typename T>
+concept ToStrStatic =
+    !std::is_void_v<decltype(std::declval<T>().template operator()<char>(
+        std::declval<typename T::type>()))>;
+
+template <typename T>
+concept ToStrFixed = !std::is_void_v<decltype(std::declval<T>()(
+    std::declval<char*>(), std::declval<typename T::type>()))>;
+
+} // namespace detail
+
+template <typename T>
+struct ToStrAdap : T
+{};
+
+template <detail::ToStrStatic T>
+struct ToStrAdap<T> : T
+{
+    using T::operator();
+
+    template <typename CharT>
+    constexpr CharT* operator()(CharT* base, const T::type& t) const
+    {
+        auto sv = (*this).template operator()<CharT>(t);
+        return std::copy(sv.begin(), sv.end(), base);
+    }
+
+    template <typename CharT>
+    constexpr void operator()(stdplus::BasicStrBuf<CharT>& buf,
+                              const T::type& t) const
+    {
+        auto ptr = buf.append(T::buf_size);
+        buf.shrink(T::buf_size - ((*this)(ptr, t) - ptr));
+    }
+};
+
+template <detail::ToStrFixed T>
+struct ToStrAdap<T> : T
+{
+    using T::operator();
+
+    template <typename CharT>
+    constexpr void operator()(stdplus::BasicStrBuf<CharT>& buf,
+                              const T::type& t) const
+    {
+        auto ptr = buf.append(T::buf_size);
+        buf.shrink(T::buf_size - ((*this)(ptr, t) - ptr));
+    }
+};
+
+template <typename T, typename CharT = char>
+struct ToStrHandle
+{
+  private:
+    stdplus::BasicStrBuf<CharT> buf;
+
+  public:
+    constexpr std::basic_string_view<CharT> operator()(const T::type& v)
+    {
+        buf.reset();
+        T{}(buf, v);
+        return buf;
+    }
+};
+
+template <detail::ToStrStatic T, typename CharT>
+struct ToStrHandle<T, CharT>
+{
+    static_assert(T::buf_size > 0);
+
+    constexpr std::basic_string_view<CharT>
+        operator()(const T::type& v) noexcept(
+            noexcept(std::declval<T>().template operator()<CharT>(
+                std::declval<typename T::type>())))
+    {
+        return T{}.template operator()<CharT>(v);
+    }
+};
+
+template <detail::ToStrFixed T, typename CharT>
+struct ToStrHandle<T, CharT>
+{
+  private:
+    std::array<CharT, T::buf_size> buf;
+
+  public:
+    constexpr std::basic_string_view<CharT>
+        operator()(const T::type& v) noexcept(noexcept(std::declval<T>()(
+            std::declval<CharT*>(), std::declval<typename T::type>())))
+    {
+        return {buf.data(), T{}(buf.data(), v)};
+    }
+};
+
+template <typename T, typename CharT>
+struct Format
+{
+  private:
+    fmt::formatter<std::basic_string_view<CharT>> formatter;
+
+  public:
+    template <typename ParseContext>
+    constexpr auto parse(ParseContext& ctx)
+    {
+        return ctx.begin();
+    }
+
+    template <typename FormatContext>
+    auto format(auto v, FormatContext& ctx) const
+    {
+        return formatter.format(ToStrHandle<T, CharT>{}(v), ctx);
+    }
+};
+
+template <typename CharT, typename T>
+constexpr auto toBasicStr(const T& t)
+{
+    return std::basic_string<CharT>(ToStrHandle<ToStr<T>, CharT>{}(t));
+}
+
+template <typename T>
+constexpr auto toStr(const T& t)
+{
+    return toBasicStr<char>(t);
+}
+
+} // namespace stdplus
diff --git a/src/meson.build b/src/meson.build
index a064f14..93d68f3 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -60,6 +60,7 @@
   'str/buf.cpp',
   'str/cat.cpp',
   'str/cexpr.cpp',
+  'str/conv.cpp',
   'str/maps.cpp',
   'util/cexec.cpp',
   'variant.cpp',
diff --git a/src/str/conv.cpp b/src/str/conv.cpp
new file mode 100644
index 0000000..22897a0
--- /dev/null
+++ b/src/str/conv.cpp
@@ -0,0 +1 @@
+#include <stdplus/str/conv.hpp>
diff --git a/test/meson.build b/test/meson.build
index a92e411..1239296 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -16,6 +16,7 @@
   'str/buf': [stdplus_dep, gtest_main_dep],
   'str/cat': [stdplus_dep, gtest_main_dep],
   'str/cexpr': [stdplus_dep, gtest_main_dep],
+  'str/conv': [stdplus_dep, gmock_dep, gtest_main_dep],
   'str/maps': [stdplus_dep, gmock_dep, gtest_main_dep],
   'util/cexec': [stdplus_dep, gtest_main_dep],
   'variant': [stdplus_dep, gtest_main_dep],
diff --git a/test/str/buf.cpp b/test/str/buf.cpp
index dc94e2c..904a59d 100644
--- a/test/str/buf.cpp
+++ b/test/str/buf.cpp
@@ -18,7 +18,6 @@
 }
 
 constexpr auto data = cexprSv<[]() { return makeIncStr(sizeof(StrBuf) + 10); }>;
-// constexpr auto data = cexprSv<[]() { return makeIncStr(32); }>;
 
 TEST(StrBuf, BasicInline)
 {
diff --git a/test/str/conv.cpp b/test/str/conv.cpp
new file mode 100644
index 0000000..3a2f41c
--- /dev/null
+++ b/test/str/conv.cpp
@@ -0,0 +1,163 @@
+#include <fmt/format.h>
+
+#include <stdplus/str/conv.hpp>
+
+#include <algorithm>
+#include <string_view>
+
+#include <gtest/gtest.h>
+
+namespace stdplus
+{
+
+struct TestValS
+{
+    static inline constexpr std::string_view value = "test";
+    static inline constexpr std::wstring_view wvalue = L"test";
+};
+
+template <>
+struct ToStr<TestValS>
+{
+    using type = TestValS;
+    static inline constexpr std::size_t buf_size = TestValS::value.size();
+
+    template <typename CharT>
+    constexpr std::basic_string_view<CharT> operator()(TestValS) const noexcept
+    {
+        if constexpr (std::is_same_v<char, CharT>)
+        {
+            return TestValS::value;
+        }
+        else
+        {
+            static_assert(std::is_same_v<wchar_t, CharT>);
+            return TestValS::wvalue;
+        }
+    }
+};
+
+static_assert(!detail::ToStrFixed<ToStr<TestValS>>);
+static_assert(detail::ToStrStatic<ToStr<TestValS>>);
+
+struct TestValF : TestValS
+{};
+
+template <>
+struct ToStr<TestValF>
+{
+    using type = TestValF;
+    static inline constexpr std::size_t buf_size = TestValF::value.size();
+
+    template <typename CharT>
+    constexpr CharT* operator()(CharT* buf, TestValF) const noexcept
+    {
+        return std::copy(TestValF::value.begin(), TestValF::value.end(), buf);
+    }
+};
+
+static_assert(detail::ToStrFixed<ToStr<TestValF>>);
+static_assert(!detail::ToStrStatic<ToStr<TestValF>>);
+
+struct TestValD : TestValS
+{};
+
+template <>
+struct ToStr<TestValD>
+{
+    using type = TestValD;
+
+    template <typename CharT>
+    constexpr void operator()(stdplus::BasicStrBuf<CharT>& buf, TestValD) const
+    {
+        auto ptr = buf.append(TestValD::value.size());
+        std::copy(TestValD::value.begin(), TestValD::value.end(), ptr);
+    }
+};
+
+static_assert(!detail::ToStrFixed<ToStr<TestValD>>);
+static_assert(!detail::ToStrStatic<ToStr<TestValD>>);
+
+} // namespace stdplus
+
+template <typename CharT>
+struct fmt::formatter<stdplus::TestValS, CharT> :
+    stdplus::Format<stdplus::ToStr<stdplus::TestValS>, CharT>
+{};
+
+template <typename CharT>
+struct fmt::formatter<stdplus::TestValF, CharT> :
+    stdplus::Format<stdplus::ToStr<stdplus::TestValF>, CharT>
+{};
+
+template <typename CharT>
+struct fmt::formatter<stdplus::TestValD, CharT> :
+    stdplus::Format<stdplus::ToStr<stdplus::TestValD>, CharT>
+{};
+
+namespace stdplus
+{
+
+TEST(ToStrAdapter, Static)
+{
+    StrBuf buf;
+    ToStrAdap<ToStr<TestValS>>{}(buf, TestValS{});
+    EXPECT_EQ("test", buf);
+    buf.reset();
+    auto ptr = buf.append(4);
+    EXPECT_EQ(4, ToStrAdap<ToStr<TestValS>>{}(ptr, TestValS{}) - ptr);
+    EXPECT_EQ("test", std::string_view(ptr, 4));
+}
+
+TEST(ToStrAdapter, Fixed)
+{
+    StrBuf buf;
+    ToStrAdap<ToStr<TestValF>>{}(buf, TestValF{});
+    EXPECT_EQ("test", buf);
+}
+
+TEST(ToStrAdapter, Dynamic)
+{
+    StrBuf buf;
+    ToStrAdap<ToStr<TestValD>>{}(buf, TestValD{});
+    EXPECT_EQ("test", buf);
+}
+
+TEST(ToStrHandle, Basic)
+{
+    EXPECT_EQ("test", (ToStrHandle<ToStr<TestValS>>{}({})));
+    EXPECT_EQ(L"test", (ToStrHandle<ToStr<TestValS>, wchar_t>{}({})));
+    EXPECT_EQ("test", (ToStrHandle<ToStr<TestValF>>{}({})));
+    EXPECT_EQ(L"test", (ToStrHandle<ToStr<TestValF>, wchar_t>{}({})));
+    EXPECT_EQ("test", (ToStrHandle<ToStr<TestValD>>{}({})));
+    EXPECT_EQ(L"test", (ToStrHandle<ToStr<TestValD>, wchar_t>{}({})));
+}
+
+TEST(Format, Basic)
+{
+    EXPECT_EQ("t test", fmt::format("t {}", TestValS{}));
+    EXPECT_EQ("t test", fmt::format("t {}", TestValF{}));
+    EXPECT_EQ("t test", fmt::format("t {}", TestValD{}));
+}
+
+template <>
+struct FromStr<TestValS>
+{
+    template <typename CharT>
+    constexpr TestValS operator()(std::basic_string_view<CharT> sv) const
+    {
+        if (sv == TestValS::value)
+        {
+            return TestValS{};
+        }
+        throw std::runtime_error("Invalid TestValS");
+    }
+};
+
+TEST(FromStr, Basic)
+{
+    EXPECT_NO_THROW(fromStr<TestValS>("test"));
+    EXPECT_THROW(fromStr<TestValS>("hi"), std::runtime_error);
+}
+
+} // namespace stdplus