1313 */
1414
1515#include < algorithm>
16+ #include < array>
1617#include < atomic>
1718#include < cassert>
1819#include < expected>
2526#include < libchess/uci/Printing.hpp>
2627#include < libutil/Strings.hpp>
2728#include < print>
29+ #include < ranges>
2830#include < string>
2931#include < string_view>
3032#include < utility>
33+ #include < vector>
3134
3235namespace chess ::uci {
3336
@@ -48,6 +51,29 @@ using util::strings::trim;
4851// defined out-of-line to address -Wweak-vtables
4952EngineBase::~EngineBase () = default ;
5053
54+ namespace {
55+ // returns name of closest known command
56+ [[nodiscard]] auto find_nearest_command (
57+ const string_view input, const EngineBase::CommandList standardCommands, const EngineBase::CommandList customCommands)
58+ -> string_view
59+ {
60+ // map commands to pair of: command name, Levenshtein distance from input
61+ const auto mapped
62+ = std::views::join (std::array { standardCommands, customCommands })
63+ | std::views::transform ([input](const EngineCommand& command) {
64+ return std::make_pair (
65+ command.name ,
66+ util::strings::levenshtein_distance (input, command.name ));
67+ })
68+ | std::ranges::to<std::vector>();
69+
70+ const auto closest = std::ranges::min (
71+ mapped, std::ranges::less { }, [](const auto & item) { return item.second ; });
72+
73+ return closest.first ;
74+ }
75+ } // namespace
76+
5177void EngineBase::handle_command (const string_view command)
5278{
5379 auto [firstWord, rest] = split_at_first_space (command);
@@ -72,7 +98,12 @@ void EngineBase::handle_command(const string_view command)
7298 return ;
7399 }
74100
75- info_string (std::format (" Unknown UCI command: '{}'" , firstWord));
101+ info_string (std::format (
102+ " Unknown UCI command: '{}'" , firstWord));
103+
104+ info_string (std::format (
105+ " The closest known command is: {}" ,
106+ find_nearest_command (firstWord, standardUCICommands, customCommands)));
76107}
77108
78109void EngineBase::respond_to_uci ()
@@ -154,6 +185,29 @@ void EngineBase::handle_setpos(const string_view arguments)
154185 });
155186}
156187
188+ namespace {
189+ // returns name of closest known option
190+ [[nodiscard]] auto find_nearest_option (
191+ const string_view input, const EngineBase::OptionList standardOptions, const EngineBase::OptionList customOptions)
192+ -> string_view
193+ {
194+ // map options to pair of: option name, Levenshtein distance from input
195+ const auto mapped
196+ = std::views::join (std::array { standardOptions, customOptions })
197+ | std::views::transform ([input](const Option* option) {
198+ return std::make_pair (
199+ option->get_name (),
200+ util::strings::levenshtein_distance (input, option->get_name ()));
201+ })
202+ | std::ranges::to<std::vector>();
203+
204+ const auto closest = std::ranges::min (
205+ mapped, std::ranges::less { }, [](const auto & item) { return item.second ; });
206+
207+ return closest.first ;
208+ }
209+ } // namespace
210+
157211void EngineBase::handle_setoption (const string_view arguments)
158212{
159213 auto [firstWord, rest] = split_at_first_space (arguments);
@@ -205,7 +259,12 @@ void EngineBase::handle_setoption(const string_view arguments)
205259 if (update_option (get_custom_uci_options ()))
206260 return ;
207261
208- info_string (std::format (" Attempted to set unknown option '{}'" , name));
262+ info_string (std::format (
263+ " Attempted to set unknown option '{}'" , name));
264+
265+ info_string (std::format (
266+ " The closest known option is: {}" ,
267+ find_nearest_option (name, standardUCIOptions, get_custom_uci_options ())));
209268}
210269
211270void EngineBase::loop ()
0 commit comments