diff --git a/include/leven.h b/include/leven.h index 6f9495b..9b9e8f4 100644 --- a/include/leven.h +++ b/include/leven.h @@ -102,12 +102,30 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] { else return leven_diff(std::string_view{sa}, std::string_view{sb}); } + class OrderedString { + public: + constexpr OrderedString() noexcept = default; + constexpr OrderedString(OrderedString const&) noexcept = default; + constexpr OrderedString(OrderedString &&) noexcept = default; + constexpr OrderedString& operator=(OrderedString const&) noexcept = default; + constexpr OrderedString& operator=(OrderedString &&) noexcept = default; + constexpr virtual ~OrderedString() = default; + + constexpr virtual std::string_view view() const noexcept = 0; + constexpr virtual size_t size() const noexcept { return view().size(); } + + constexpr operator std::string_view() const noexcept { return view(); } + }; + /// String that is ordered by Levenshtein distance. /// /// Used as sorted map key for finding suggestions for possible typos. All valid strings are added as `string_ord`, and the invalid string lookup looks for its closest neighbour(s), those are presented to the user in a "did you mean ..." format with the lowest distance first and so on. template S> struct string_ord { using string_type = std::remove_reference_t; + constexpr static inline bool is_array = std::is_array_v; + constexpr static size_t array_size() noexcept requires(is_array) { return sizeof(string_type) - 1; } + //constexpr static inline size_t ARRAY_SIZE = array_size(); template requires(std::is_constructible_v) constexpr string_ord(Args&&... args) noexcept(std::is_nothrow_constructible_v) @@ -134,13 +152,16 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] { constexpr auto difference_to(std::convertible_to auto const& str) const noexcept { return leven_diff(std::forward(str), m_string); } - constexpr friend auto operator<=>(const string_ord& a, const string_ord& b) noexcept { + + template S2> + constexpr friend auto operator<=>(const string_ord& a, const string_ord& b) noexcept { auto ab = leven_diff(a.m_string, b.m_string); auto ba = leven_diff(b.m_string, a.m_string); return ab <=> ba; } constexpr std::string_view view() const noexcept { if constexpr(std::is_array_v) return { m_string, sizeof(string_type) - 1 }; else return { m_string }; } + constexpr size_t size() const noexcept { if constexpr(std::is_array_v) return sizeof(string_type) - 1; else return view().size(); } constexpr operator std::string_view() const noexcept { return view(); } template requires(std::is_convertible_v and std::is_convertible_v) @@ -161,6 +182,46 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] { constexpr string_type&& operator*() && noexcept { return std::move(m_string); } constexpr string_type const&& operator*() const&& noexcept { return std::move(m_string); } + + constexpr decltype(auto) make_dynamic() const noexcept requires(std::is_copy_constructible_v>){ + return string_ord{ *this }.make_dynamic(); + } + constexpr auto make_dynamic() && noexcept { + if constexpr(std::is_array_v) { + struct OS : public OrderedString { + std::array str; + + constexpr virtual ~OS() = default; + constexpr OS(string_type const& str) noexcept : OrderedString(), str(util::array_literal(std::forward(str))) {} + constexpr OS(const OS&) = default; + constexpr OS(OS&&) = default; + + + constexpr OS& operator=(const OS&) = default; + constexpr OS& operator=(OS &&) = default; + + constexpr virtual size_t size() const noexcept override { return sizeof(string_type)-1; } + constexpr virtual std::string_view view() const noexcept override { return { str.data(), sizeof(string_type)-1 }; } + }; + return OS { std::move(m_string) }; + } else { + struct OS : public OrderedString { + string_type str; + + constexpr virtual ~OS() = default; + constexpr OS(string_type&& str) noexcept : OrderedString(), str(std::move(str)) {} + constexpr OS(const OS&) = default; + constexpr OS(OS&&) = default; + + constexpr virtual std::string_view view() const noexcept override { return { str }; } + + constexpr OS& operator=(const OS&) = default; + constexpr OS& operator=(OS &&) = default; + + }; + return OS { std::move(m_string) }; + } + } private: template requires(sizeof...(Is) == sizeof(string_type)) constexpr string_ord(const char (&&l)[sizeof(string_type)], std::index_sequence) noexcept requires(std::is_array_v) @@ -170,6 +231,8 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] { }; template string_ord(const char (&l)[N]) -> string_ord; + template + string_ord(T&&) -> string_ord; /// Used to store all valid command names, so when an invalid one is found, the closest matching one(s) can be suggested to the user in a "did you mean ..." format with the *lowest difference* neighbour(s) to the invalid string first. template S = std::string_view> diff --git a/include/util.hh b/include/util.hh index fb8d35e..033a5dc 100644 --- a/include/util.hh +++ b/include/util.hh @@ -37,16 +37,20 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] { static_assert(_EO_CONSTANT_VALUE(std::string_view{"hello world"}) == std::string_view{"hello world"}, "Bad CONSTANT_VALUE()"); - template> - constexpr decltype(auto) array_literal(const T (&a)[N]) noexcept { + template, typename _I = std::make_index_sequence> + constexpr auto array_literal(const T (&a)[N]) noexcept + -> Array + { constexpr decltype(auto) _array_create = [](auto const& a, std::index_sequence) noexcept { return Array { a[I]... }; }; return _array_create(std::move(a), _I{}); } - template> - constexpr decltype(auto) array_literal(T (&&a)[N]) noexcept { + template, typename _I = std::make_index_sequence> + constexpr auto array_literal(T (&&a)[N]) noexcept + -> Array + { constexpr decltype(auto) _array_create = [](auto&& a, std::index_sequence) noexcept { return Array { std::move(a[I])... }; diff --git a/src/leven.cpp b/src/leven.cpp index aa19315..c9a4dcd 100644 --- a/src/leven.cpp +++ b/src/leven.cpp @@ -20,8 +20,12 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] { namespace { constexpr string_ord a("hello world"); + constexpr string_ord c{a.view()}; constexpr string_ord b{""}; static_assert(a.view()[a.view().size()-1], "Levenshtein distance size mismatch for array literals"); + static_assert(a.difference_from(c) == 0, "Levenshtein distance mismatch when converting from array literal"); + static_assert(a.make_dynamic().view() == c.make_dynamic(), "Levenshtein distance mismatch when converting from array literal to dynamic"); + static_assert(c.make_dynamic().view() == a.make_dynamic(), "Levenshtein distance mismatch when converting from std::string to dynamic"); static_assert(b.view().empty(), "Levenshtein distance size mismatch for array literals (empty)"); static_assert(a.difference_from(b) == (sizeof(a) - sizeof(b)), "Levenshtein distance size mismatch for array literals (calculation)"); }