# 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 <memory>
# 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 ) ] ) noexcept 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 ) ] ) noexcept requires ( std : : is_array_v < string_type > )
: string_ord ( std : : move ( str ) , std : : make_index_sequence < sizeof ( string_type ) > { } ) { }
constexpr static string_ord from_char_array ( std : : array < char , sizeof ( string_type ) > & & l ) noexcept requires ( std : : is_array_v < string_type > )
{
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 < 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 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 < 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 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 < 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 ] . . . } { }
template < size_t . . . Is > requires ( sizeof . . . ( Is ) = = sizeof ( string_type ) )
constexpr explicit string_ord ( std : : array < char , sizeof ( string_type ) > & & l , 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 > ;
/// Create a dynamic instance of `string_ord<T>`.
/// This should be passed immediately to a call to `std::make_{unique/shared}` for most cases
template < typename T , typename . . . Args > requires ( std : : is_convertible_v < T , std : : string_view > and std : : is_constructible_v < T , Args . . . > )
constexpr decltype ( auto ) make_dynamic_ordered_string ( Args & & . . . ctor ) noexcept ( std : : is_nothrow_constructible_v < T , Args . . . > )
{
using str = string_ord < T > ;
return str { std : : forward < Args > ( ctor ) . . . } . make_dynamic ( ) ;
}
template < std : : convertible_to < std : : string_view > T >
constexpr decltype ( auto ) make_dynamic_ordered_string ( T & & value ) noexcept ( std : : is_nothrow_move_constructible_v < T > )
{
return string_ord < T > { std : : move ( value ) } . make_dynamic ( ) ;
}
template < std : : convertible_to < std : : string_view > T >
constexpr auto make_unique_ordered_string ( T & & value ) noexcept ( std : : is_nothrow_move_constructible_v < T > )
{
auto & & tmp = make_dynamic_ordered_string < T > ( std : : move ( value ) ) ;
return std : : make_unique < std : : remove_reference_t < decltype ( tmp ) > > ( std : : move ( tmp ) ) ;
}
template < std : : convertible_to < std : : string_view > T >
constexpr auto make_shared_ordered_string ( T & & value ) noexcept ( std : : is_nothrow_move_constructible_v < T > )
{
auto & & tmp = make_dynamic_ordered_string < T > ( std : : move ( value ) ) ;
return std : : make_shared < std : : remove_reference_t < decltype ( tmp ) > > ( std : : move ( tmp ) ) ;
}
template < typename T , typename . . . Args > requires ( std : : is_convertible_v < T , std : : string_view > and std : : is_constructible_v < T , Args . . . > )
constexpr auto make_ordered_string ( Args & & . . . args ) noexcept ( std : : is_nothrow_constructible_v < T , Args . . . > )
{
auto dyn = make_dynamic_ordered_string < T , Args . . . > ( std : : forward < Args > ( args ) . . . ) ;
struct mover {
using type = std : : remove_reference_t < decltype ( dyn ) > ;
using string_type = decltype ( dyn . str ) ;
type value ;
/* constexpr explicit operator auto() noexcept {
if constexpr ( type : : is_array ( ) )
return string_ord < string_type > : : from_char_array ( std : : move ( value . str ) ) ;
else return string_ord < string_type > { std : : move ( value . str ) } ;
} */
constexpr operator std : : shared_ptr < type > ( ) noexcept { return std : : make_shared < type > ( std : : move ( value ) ) ; }
constexpr operator std : : unique_ptr < type > ( ) & & noexcept { return std : : make_unique < type > ( std : : move ( value ) ) ; }
constexpr std : : unique_ptr < type > unique ( ) & & noexcept { return std : : make_unique < type > ( std : : move ( value ) ) ; }
constexpr std : : shared_ptr < type > shared ( ) noexcept { return this - > operator std : : shared_ptr < type > ( ) ; }
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 < 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 */