@ -9,6 +9,7 @@
# include <string_view>
# include <string_view>
# include <concepts>
# include <concepts>
# include <vector>
# include <vector>
# include <map>
# include <array>
# include <array>
# include <span>
# include <span>
# include <tuple>
# include <tuple>
@ -22,6 +23,8 @@ size_t _EO(leven_diff)(const char* _EO(restrict), const char* _EO(restrict), siz
__attribute__ ( ( nonnull ( 1 , 2 ) ) )
__attribute__ ( ( nonnull ( 1 , 2 ) ) )
;
;
//TODO: C interface for `string_ord` and `sim_map`?
# ifdef __cplusplus
# ifdef __cplusplus
}
}
@ -79,17 +82,68 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
return leven_diff ( str { sa } , str { sb } ) ;
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 >
template < size_t M , size_t N >
constexpr decltype ( auto ) leven_diff ( const char ( & sa ) [ M ] , const char ( & sb ) [ N ] ) noexcept {
constexpr decltype ( auto ) leven_diff ( const char ( & sa ) [ M ] , const char ( & sb ) [ N ] ) noexcept {
if constexpr ( ! N ) return M ;
if constexpr ( ! N ) return M ;
else if constexpr ( ! M ) return N ;
else if constexpr ( ! M ) return N ;
else if constexpr ( M < N ) return N - M ; //XXX: Are these lines correct?
//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 if constexpr(N < M) return M - N; // ^
else return leven_diff < N > ( std : : string_view { sa } , std : : string_view { sb } ) ;
else return leven_diff < N > ( 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 < std : : convertible_to < std : : string_view > S >
struct string_ord {
using string_type = std : : remove_reference_t < S > ;
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 < 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 ) ;
}
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 < typename T , std : : convertible_to < std : : string_view > S = std : : string_view >
using sim_map = std : : map < string_ord < S > , T > ;
} // ::util //
}
# endif
# endif
# endif /* _LEVEN_H */
# endif /* _LEVEN_H */