#ifndef _LEVEN_H #define _LEVEN_H #include "exopt.h" #ifdef __cplusplus #include "util.hh" #include #include #include #include #include #include #include #include extern "C" { #endif size_t _EO(leven_diff)(const char* _EO(restrict), const char* _EO(restrict), size_t) _EO(internal) _EO(readonly) __attribute__((nonnull(1, 2))) ; //TODO: C interface for `string_ord` and `sim_map`? #ifdef __cplusplus } namespace exopt { namespace util [[gnu::visibility("internal")]] { template // NOTE: Do not change this manually // constexpr auto leven_diff(std::string_view s1, std::string_view s2) noexcept { const size_t m(s1.size()), n(s2.size()); if(__builtin_expect(!m, false)) return n; if(__builtin_expect(!n, false)) return m; auto&& work_on_d = [&](auto&& costs) { for(size_t i=0;i <= n; i++) costs[i] = i; size_t i{0}; for(auto const& c1: s1) { costs[0] = i + 1; size_t corner { i }, j { 0 }; for(auto const& c2: s2) { size_t upper { costs[j+1] }; if( c1 == c2 ) costs[j+1] = corner; else { using std::min; size_t t{min(upper, corner)};//upper 0) { return work_on_d(std::array{}); } else if consteval { return work_on_d(std::vector(n + 1)); } else { thread_local static std::vector d; const size_t n1 = n + 1; if(__builtin_expect(d.size() < n1, false)) d.resize(n1); // We don't need to clear the buffer, it will be reinitialised by `work_on_d()`. return work_on_d(std::span{d.begin(), n1}); } } template S1, std::convertible_to S2> constexpr decltype(auto) leven_diff(const S1& sa, const S2& sb) noexcept { using str = std::string_view; return leven_diff(str{sa}, str{sb}); } //TODO: Add overloads for when one bound is known statically but the other is not. Would this be useful? template constexpr decltype(auto) leven_diff(const char (&sa)[M], const char (&sb)[N]) noexcept { if constexpr(!N) return M; else if constexpr(!M) return N; else if constexpr(M < N) return N - M; else if constexpr(N < M) return M - N; else return leven_diff(std::string_view{sa}, std::string_view{sb}); } template constexpr decltype(auto) leven_diff(std::array const& sa, std::array const& sb) noexcept { if constexpr(!N) return M; else if constexpr(!M) return N; else if constexpr(M < N) return N - M; else if constexpr(N < M) return M - N; 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) : m_string{std::forward(args)...} {} // { return { S(std::forward(args)...) }; } constexpr string_ord(std::add_rvalue_reference_t str) noexcept requires(!std::is_array_v) : m_string(std::move(str)) {} constexpr string_ord(const char (&str)[sizeof(string_type)]) noexcept requires(std::is_array_v) : string_ord(std::move(str), std::make_index_sequence{}) {} constexpr string_ord(const char (&&str)[sizeof(string_type)]) noexcept requires(std::is_array_v) : string_ord(std::move(str), std::make_index_sequence{}) {} constexpr static string_ord from_char_array(std::array&& l) noexcept requires(std::is_array_v) { return { std::move(l) }; } constexpr string_ord(string_ord const&) = default; constexpr string_ord(string_ord &&) = default; constexpr string_ord& operator=(string_ord const&) = default; constexpr string_ord& operator=(string_ord &&) = default; constexpr ~string_ord() = default; constexpr auto difference_from(std::convertible_to auto const& str) const noexcept { return leven_diff(m_string, std::forward(str)); } constexpr auto difference_to(std::convertible_to auto const& str) const noexcept { return leven_diff(std::forward(str), m_string); } 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) constexpr explicit operator string_ord() && noexcept { return { std::move(m_string) }; } template requires(std::is_convertible_v and std::is_convertible_v and std::is_constructible_v) constexpr explicit operator string_ord() const&& noexcept { return { U{std::move(m_string)} }; } template requires(std::is_convertible_v and std::is_convertible_v and std::is_constructible_v) constexpr explicit operator string_ord() const& noexcept { return { U{m_string} }; } constexpr string_type& string() noexcept { return m_string; } constexpr string_type const& string() const noexcept { return m_string; } constexpr string_type& operator*() & noexcept { return m_string; } constexpr string_type const& operator*() const& noexcept { return m_string; } 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 static bool is_array() noexcept { return true; } 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 static bool is_array() noexcept { return false; } 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) : m_string { l[Is]... } {} template requires(sizeof...(Is) == sizeof(string_type)) constexpr explicit string_ord(std::array&& l, std::index_sequence) noexcept requires(std::is_array_v) : m_string { l[Is]... } {} string_type m_string; }; template string_ord(const char (&l)[N]) -> string_ord; template string_ord(T&&) -> string_ord; /// Create a dynamic instance of `string_ord`. /// This should be passed immediately to a call to `std::make_{unique/shared}` for most cases template requires(std::is_convertible_v and std::is_constructible_v) constexpr decltype(auto) make_dynamic_ordered_string(Args&&... ctor) noexcept(std::is_nothrow_constructible_v) { using str = string_ord; return str{ std::forward(ctor)... }.make_dynamic(); } template T> constexpr decltype(auto) make_dynamic_ordered_string(T&& value) noexcept(std::is_nothrow_move_constructible_v) { return string_ord{ std::move(value) }.make_dynamic(); } template T> constexpr auto make_unique_ordered_string(T&& value) noexcept(std::is_nothrow_move_constructible_v) { auto&& tmp = make_dynamic_ordered_string(std::move(value)); return std::make_unique>(std::move(tmp)); } template T> constexpr auto make_shared_ordered_string(T&& value) noexcept(std::is_nothrow_move_constructible_v) { auto&& tmp = make_dynamic_ordered_string(std::move(value)); return std::make_shared>(std::move(tmp)); } template requires(std::is_convertible_v and std::is_constructible_v) constexpr auto make_ordered_string(Args&&... args) noexcept(std::is_nothrow_constructible_v) { auto dyn = make_dynamic_ordered_string(std::forward(args)...); struct mover { using type = std::remove_reference_t; using string_type = decltype(dyn.str); type value; /* constexpr explicit operator auto() noexcept { if constexpr(type::is_array()) return string_ord::from_char_array(std::move(value.str)); else return string_ord{ std::move(value.str) }; }*/ constexpr operator std::shared_ptr() noexcept { return std::make_shared(std::move(value)); } constexpr operator std::unique_ptr() && noexcept { return std::make_unique(std::move(value)); } constexpr std::unique_ptr unique() && noexcept { return std::make_unique(std::move(value)); } constexpr std::shared_ptr shared() noexcept { return this-> operator std::shared_ptr(); } constexpr ~mover() = default; /* constexpr string_type const&& exposed() const&& noexcept requires(!type::is_array()){ return std::move(value.str); } constexpr string_type const& exposed() const& noexcept requires(!type::is_array()){ return value.str; } constexpr string_type&& exposed() && noexcept requires(!type::is_array()) { return std::move(value.str); } constexpr string_type exposed() & noexcept requires(!type::is_array()){ return value.str; } //constexpr string_type const& exposed() const noexcept { return value.str; } constexpr explicit operator type&&() && noexcept { return std::move(value); } constexpr explicit operator string_type&&() && noexcept requires(!type::is_array()) { return std::move(value.str); } constexpr explicit operator std::string_view() const noexcept { return { value }; }*/ }; return mover{std::move(dyn)}; } /// 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> using sim_map = std::map, T>; } // ::util // } #endif #endif /* _LEVEN_H */