diff --git a/include/leven.h b/include/leven.h index c9ee7cc..0deb382 100644 --- a/include/leven.h +++ b/include/leven.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,8 @@ size_t _EO(leven_diff)(const char* _EO(restrict), const char* _EO(restrict), siz __attribute__((nonnull(1, 2))) ; +//TODO: C interface for `string_ord` and `sim_map`? + #ifdef __cplusplus } @@ -79,17 +82,68 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] { 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; //XXX: Are these lines correct? - else if constexpr(N < M) return M - N; // ^ + //else if constexpr(M < N) return N - M; // These lines are not correct, just fixing the sizes does not make the edit valid + //else if constexpr(N < M) return M - N; // ^ else return leven_diff(std::string_view{sa}, std::string_view{sb}); } -} } + /// 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 string_ord(string_type&& str) noexcept + : m_string(std::move(str)) {} + + 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); + } + 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 operator std::string_view() const noexcept { return 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); } + + private: + string_type m_string; + }; + + /// 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 */