You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
248 lines
9.6 KiB
248 lines
9.6 KiB
#ifndef _LEVEN_H
|
|
#define _LEVEN_H
|
|
|
|
#include "exopt.h"
|
|
|
|
#ifdef __cplusplus
|
|
#include "util.hh"
|
|
|
|
#include <string_view>
|
|
#include <concepts>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <array>
|
|
#include <span>
|
|
#include <tuple>
|
|
|
|
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<size_t N =0> // 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<corner ? upper : corner
|
|
costs[j+1] = min(costs[j], t) + 1;
|
|
|
|
}
|
|
corner = upper;
|
|
j += 1;
|
|
}
|
|
i += 1;
|
|
}
|
|
return costs[n];
|
|
};
|
|
|
|
if constexpr(N > 0) {
|
|
return work_on_d(std::array<size_t, N+1>{});
|
|
} else if consteval {
|
|
return work_on_d(std::vector<size_t>(n + 1));
|
|
} else {
|
|
thread_local static std::vector<size_t> 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<size_t>{d.begin(), n1});
|
|
}
|
|
}
|
|
template<std::convertible_to<std::string_view> S1, std::convertible_to<std::string_view> 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<size_t M, size_t N>
|
|
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<N>(std::string_view{sa}, std::string_view{sb});
|
|
}
|
|
template<size_t M, size_t N>
|
|
constexpr decltype(auto) leven_diff(std::array<char, M> const& sa, std::array<char, N> 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<N>(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<std::convertible_to<std::string_view> S>
|
|
struct string_ord {
|
|
using string_type = std::remove_reference_t<S>;
|
|
constexpr static inline bool is_array = std::is_array_v<string_type>;
|
|
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<typename... Args> requires(std::is_constructible_v<string_type, Args...>)
|
|
constexpr string_ord(Args&&... args) noexcept(std::is_nothrow_constructible_v<string_type, Args...>)
|
|
: m_string{std::forward<Args>(args)...} {}
|
|
// { return { S(std::forward<Args>(args)...) }; }
|
|
constexpr string_ord(std::add_rvalue_reference_t<S> str) noexcept requires(!std::is_array_v<string_type>)
|
|
: m_string(std::move(str)) {}
|
|
|
|
constexpr string_ord(const char (&str)[sizeof(string_type)]) requires(std::is_array_v<string_type>)
|
|
: string_ord(std::move(str), std::make_index_sequence<sizeof(string_type)>{}) {}
|
|
|
|
constexpr string_ord(const char (&&str)[sizeof(string_type)]) requires(std::is_array_v<string_type>)
|
|
: string_ord(std::move(str), std::make_index_sequence<sizeof(string_type)>{}) {}
|
|
|
|
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<std::string_view> auto const& str) const noexcept {
|
|
return leven_diff(m_string, std::forward<decltype(str)>(str));
|
|
}
|
|
constexpr auto difference_to(std::convertible_to<std::string_view> auto const& str) const noexcept {
|
|
return leven_diff(std::forward<decltype(str)>(str), m_string);
|
|
}
|
|
|
|
template<std::convertible_to<std::string_view> S2>
|
|
constexpr friend auto operator<=>(const string_ord& a, const string_ord<S2>& 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<string_type>) return { m_string, sizeof(string_type) - 1 }; else return { m_string }; }
|
|
constexpr size_t size() const noexcept { if constexpr(std::is_array_v<string_type>) return sizeof(string_type) - 1; else return view().size(); }
|
|
constexpr operator std::string_view() const noexcept { return view(); }
|
|
|
|
template<typename U> requires(std::is_convertible_v<U, std::string_view> and std::is_convertible_v<string_type&&, U>)
|
|
constexpr explicit operator string_ord<U>() && noexcept { return { std::move(m_string) }; }
|
|
|
|
template<typename U> requires(std::is_convertible_v<U, std::string_view> and std::is_convertible_v<string_type&&, U> and std::is_constructible_v<U, string_type const&&>)
|
|
constexpr explicit operator string_ord<U>() const&& noexcept { return { U{std::move(m_string)} }; }
|
|
|
|
template<typename U> requires(std::is_convertible_v<U, std::string_view> and std::is_convertible_v<string_type&&, U> and std::is_constructible_v<U, string_type const&>)
|
|
constexpr explicit operator string_ord<U>() 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<string_ord<S>>){
|
|
return string_ord<S>{ *this }.make_dynamic();
|
|
}
|
|
constexpr auto make_dynamic() && noexcept {
|
|
if constexpr(std::is_array_v<string_type>) {
|
|
struct OS : public OrderedString {
|
|
std::array<char, sizeof(string_type)> str;
|
|
|
|
constexpr virtual ~OS() = default;
|
|
constexpr OS(string_type const& str) noexcept : OrderedString(), str(util::array_literal(std::forward<decltype(str)>(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<size_t... Is> requires(sizeof...(Is) == sizeof(string_type))
|
|
constexpr string_ord(const char (&&l)[sizeof(string_type)], std::index_sequence<Is...>) noexcept requires(std::is_array_v<string_type>)
|
|
: m_string { l[Is]... } {}
|
|
|
|
string_type m_string;
|
|
};
|
|
template<size_t N>
|
|
string_ord(const char (&l)[N]) -> string_ord<decltype(l)>;
|
|
template<typename T>
|
|
string_ord(T&&) -> string_ord<T>;
|
|
|
|
/// 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<typename T, std::convertible_to<std::string_view> S = std::string_view>
|
|
using sim_map = std::map<string_ord<S>, T>;
|
|
|
|
} // ::util //
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
#endif /* _LEVEN_H */
|