17 virtual bool match(
const std::string& val)
const = 0;
22 bool match(
const std::string& val)
const;
33 template<
class Target>
39 virtual void loadInto(
const std::string& token, Target& target)
const = 0;
44 virtual Target
marshal(
const std::string& token)
const;
48#define _INTEGER_OPTION_PARSER(tp) template<class Move> Info<Move> parse##tp##Info( const std::vector<std::string> & tokens, size_t & i) \
49{return parseIntegerInfo( InfoFactory<Move>::make##tp##Info , tokens, i);}
51 namespace _UCI_info_parsers {
53 class UciParsingException :
public std::exception {};
56 Info<Move> parseIntegerInfo(Info<Move>(*factoryMethod)(int32_t v),
const std::vector<std::string>& tokens,
size_t& i) {
57 if (i >= tokens.size()) {
58 throw UciParsingException();
61 std::string value_string = tokens[i++];
64 if ((std::stringstream(value_string) >> value).fail()) {
65 throw UciParsingException();
68 return factoryMethod(value);
71 _INTEGER_OPTION_PARSER(Depth);
72 _INTEGER_OPTION_PARSER(Seldepth);
73 _INTEGER_OPTION_PARSER(Time);
74 _INTEGER_OPTION_PARSER(Nodes);
75 _INTEGER_OPTION_PARSER(MultiPv);
76 _INTEGER_OPTION_PARSER(CurrentMoveNumber);
77 _INTEGER_OPTION_PARSER(Hashfull);
78 _INTEGER_OPTION_PARSER(NodesPerSecond);
79 _INTEGER_OPTION_PARSER(Tbhiths);
80 _INTEGER_OPTION_PARSER(Sbhits);
81 _INTEGER_OPTION_PARSER(CPUload);
84 Info<Move> parseStringInfo(
const std::vector<std::string>& tokens,
size_t& i) {
86 if (i >= tokens.size()) {
87 throw UciParsingException();
90 std::string accumulator = tokens[i++];
91 while (i < tokens.size())
92 accumulator +=
' ' + tokens[i++];
94 return InfoFactory<Move>::makeStringInfo(accumulator);
98 Info <Move> parsePvInfo(
const std::vector<std::string>& tokens,
size_t& i, PatternMatcher& movePatternMatcher, Marschaler<Move>& marshaler) {
99 std::vector<Move> result;
100 while (i < tokens.size()) {
101 if (!movePatternMatcher.match(tokens[i]))
104 result.push_back(marshaler.marshal(tokens[i++]));
107 return InfoFactory<Move>::makePvInfo(result);
110 template <
class Move>
111 Info <Move> parseRefutationInfo(
const std::vector<std::string>& tokens,
size_t& i, PatternMatcher& movePatternMatcher, Marschaler<Move>& marshaler) {
113 std::vector<Move> result;
115 if (i >= tokens.size() || !movePatternMatcher.match(tokens[i]))
116 throw UciParsingException();
118 marshaler.loadInto(tokens[i++], m);
120 while (i < tokens.size()) {
121 if (!movePatternMatcher.match(tokens[i]))
124 result.push_back(marshaler.marshal(tokens[i++]));
127 return InfoFactory<Move>::makeRefutationInfo(m, result);
130 template <
class Move>
131 Info <Move> parseCurrlineInfo(
const std::vector<std::string>& tokens,
size_t& i, PatternMatcher& movePatternMatcher, Marschaler<Move>& marshaler) {
132 std::vector<Move> result;
134 if (i >= tokens.size())
135 throw UciParsingException();
137 std::string first = tokens[i];
140 if ((std::stringstream(first) >> cpunr).fail())
146 while (i < tokens.size()) {
147 if (!movePatternMatcher.match(tokens[i]))
150 result.push_back(marshaler.marshal(tokens[i++]));
153 return InfoFactory<Move>::makeCurrLineInfo(result, cpunr);
156 template <
class Move>
157 Info<Move> parseCurrentMoveInfo(
const std::vector<std::string>& tokens,
size_t& i, PatternMatcher& movePatternMatcher, Marschaler<Move>& marshaler) {
159 if (i >= tokens.size() || !movePatternMatcher.match(tokens[i]))
160 throw UciParsingException();
162 marshaler.loadInto(tokens[i++], result);
163 return InfoFactory<Move>::makeCurrentMoveInfo(result);
166 template <
class Move>
167 Info<Move> parseScoreInfo(
const std::vector<std::string>& tokens,
size_t& i) {
169 std::string type =
"", temp;
170 UciScore::BoundType _boundType = UciScore::Exact;
172 while (i < tokens.size()) {
175 if (temp ==
"cp" || temp ==
"mate") {
177 if (i >= tokens.size() || (std::stringstream(tokens[i++]) >> eval).fail())
178 throw UciParsingException();
181 else if (temp ==
"lowerbound")
182 _boundType = UciScore::Lowerbound;
183 else if (temp ==
"upperbound")
184 _boundType = UciScore::Upperbound;
189 if (type !=
"cp" && type !=
"mate")
190 throw UciParsingException();
193 UciScore score = (type[0] ==
'c' ? UciScore::fromCentipawns : UciScore::fromMateDistance)(eval, _boundType);
194 return InfoFactory<Move>::makeScoreInfo(score);
199#undef _INTEGER_OPTION_PARSER
202#define __RECEIVE_SIGNAL(SIGNAL) [this](Tokens & in) {this->on##SIGNAL(in);}
205 typedef const std::vector<std::string> Tokens;
206 std::shared_ptr<AbstractEngineHandler<Move>> handler;
208 std::shared_ptr<Marschaler<Move>> moveMarshaler;
209 std::shared_ptr<PatternMatcher> moveValidator;
211 std::map<std::string, std::function<void(Tokens&)>> router;
214 void onID(Tokens& payload);
215 void onBestMove(Tokens& payload);
216 void onInfo(Tokens& payload);
217 void onOption(Tokens& payload);
218 void onRegistration(Tokens& payload);
219 void onCopyprotection(Tokens& payload);
220 void onUciok(Tokens& payload);
221 void onReadyok(Tokens& payload);
224 void parseCheckOption(Tokens& payload,
size_t& i,
const std::string& option_id);
225 void parseStringOption(Tokens& payload,
size_t& i,
const std::string& option_id);
226 void parseSpinOption(Tokens& payload,
size_t& i,
const std::string& option_id);
227 void parseComboOption(Tokens& payload,
size_t& i,
const std::string& option_id);
229 void logError(
const std::string& errorMsg);
231 std::string concatTokens(Tokens& payload,
size_t start,
char sep =
' ')
const;
232 std::string notEnoughArguments(
size_t expectedArgumentCount, Tokens& payload);
233 std::string notExpectedFormat(
const std::string& expected, Tokens& found);
235 std::vector<std::string> tokenize(
const std::string& line)
const;
237 UCIParser(std::shared_ptr<AbstractEngineHandler<Move>> engineHandler, std::shared_ptr<Marschaler<Move>> marshaler, std::shared_ptr<PatternMatcher> movePatternMatcher) :
238 moveMarshaler(marshaler), moveValidator(movePatternMatcher), handler(engineHandler) {
240 {
"id", __RECEIVE_SIGNAL(ID)},
241 {
"bestmove", __RECEIVE_SIGNAL(BestMove)},
242 {
"info", __RECEIVE_SIGNAL(Info)},
243 {
"option",__RECEIVE_SIGNAL(Option)},
244 {
"registration", __RECEIVE_SIGNAL(Registration)},
245 {
"copyprotection", __RECEIVE_SIGNAL(Copyprotection)},
246 {
"readyok", __RECEIVE_SIGNAL(Readyok)},
247 {
"uciok", __RECEIVE_SIGNAL(Uciok)}
251 void parseLine(
const std::string& line);
255#undef __RECEIVE_SIGNAL
257#define CHECK_IF_HAS_NEXT_TOKEN(msg) if (i >= payload.size()) { handler->onError(msg); return; }
261 inline void UCIParser<Move>::onRegistration(Tokens& payload)
265 if (payload.size() < 2) {
266 logError(notEnoughArguments(2, payload));
271 const std::string& commandStatus = payload[1];
273 if (commandStatus ==
"ok") {
274 handler->onRegistration(ProcedureStatus::Ok);
279 if (commandStatus ==
"error") {
280 handler->onRegistration(ProcedureStatus::Error);
284 if (commandStatus ==
"checking") {
285 handler->onRegistration(ProcedureStatus::Checking);
289 logError(notExpectedFormat(
"registration [ok/checking/error]", payload));
293 inline void UCIParser<Move>::onCopyprotection(Tokens& payload)
297 if (payload.size() < 2) {
298 logError(notEnoughArguments(2, payload));
302 const std::string& commandStatus = payload[1];
304 if (commandStatus ==
"ok") {
305 handler->onCopyProtection(ProcedureStatus::Ok);
310 if (commandStatus ==
"error") {
311 handler->onCopyProtection(ProcedureStatus::Error);
315 if (commandStatus ==
"checking") {
316 handler->onCopyProtection(ProcedureStatus::Checking);
320 logError(notExpectedFormat(
"copyprotection [ok/checking/error]", payload));
324 inline void UCIParser<Move>::logError(
const std::string& errorMsg)
326 handler->onError(errorMsg);
330 inline std::string UCIParser<Move>::concatTokens(Tokens& payload,
size_t start,
char sep)
const {
332 std::string result = payload[start];
334 for (
size_t i = start + 1; i < payload.size(); ++i) {
335 result.push_back(sep);
336 result += payload[i];
343 inline std::string UCIParser<Move>::notEnoughArguments(
size_t expectedArgumentCount, Tokens& payload)
345 return std::string(
"Error! Command ") + payload[0] +
" expects " + std::to_string(expectedArgumentCount);
349 inline std::string UCIParser<Move>::notExpectedFormat(
const std::string& expected, Tokens& found)
351 return std::string(
"Format expected: ") + expected +
", found:" + concatTokens(found, 0,
' ');
355 inline std::vector<std::string> UCIParser<Move>::tokenize(
const std::string& line)
const
357 std::vector<std::string> tokens;
358 std::stringstream sstream(line);
364 if (token.size()) tokens.push_back(token);
371 inline void UCIParser<Move>::parseLine(
const std::string& line)
373 Tokens tokens = tokenize(line);
375 if (tokens.size() < 1)
return;
377 auto it = router.find(tokens[0]);
379 if (it == router.end()) {
380 logError(std::string(
"Unrecognised command: ") + tokens[0]);
390 inline void UCIParser<Move>::onUciok(Tokens& payload)
396 inline void UCIParser<Move>::onReadyok(Tokens& payload)
398 handler->onReadyOK();
402 inline void UCIParser<Move>::onID(Tokens& payload)
404 if (payload.size() < 3) {
405 logError(notEnoughArguments(3, payload));
409 if (payload[1] ==
"name") {
410 handler->onEngineName(concatTokens(payload, 2,
' '));
414 if (payload[1] ==
"author") {
415 handler->onEngineAuthor(concatTokens(payload, 2,
' '));
419 logError(notExpectedFormat(
"id name/author ...", payload));
423 inline void UCIParser<Move>::onBestMove(Tokens& payload)
427 if (payload.size() != 2 && payload.size() != 4) {
428 logError(
"Incorrect argument count received for command bestmove");
432 if (!moveValidator->match(payload[1])) {
433 logError(
"Engine send ill formatted move");
437 Move bestMove = moveMarshaler->marshal(payload[1]);
440 if (payload.size() == 4 && payload[2] ==
"ponder" && moveValidator->match(payload[3])) {
441 ponderMove = moveMarshaler->marshal(payload[3]);
442 handler->onBestMove(bestMove, ponderMove);
446 handler->onBestMove(bestMove);
451 inline void UCIParser<Move>::onOption(Tokens& payload)
456 static const std::map<std::string, OptionType> _types = {
465 if (payload.size() < 2 || payload[1] !=
"name") {
466 logError(
"Engine send option command, but 'name' token was not the 2nd argument");
472 std::string option_name, str_value, combo_default;
473 std::vector<std::string> combo_values;
474 int32_t min_ = 0, max_ = 0, default_ = 0;
477 while (i < payload.size() && payload[i] !=
"type") {
478 option_name += payload[i++];
482 CHECK_IF_HAS_NEXT_TOKEN(
"Engine send option command, but name sequence was not terminated by 'type' token.");
484 option_name.pop_back();
488 CHECK_IF_HAS_NEXT_TOKEN(
"Engine send option command, but it unexpectedly terminated after the 'type' token.")
490 auto option_type_it = _types.find(payload[i]);
492 if (option_type_it == _types.end()) {
493 handler->onError(payload[i] +
" is not recognised option type.");
499 switch (option_type_it->second) {
501 handler->onOption(Option(option_name));
break;
503 parseCheckOption(payload, i, option_name);
506 parseStringOption(payload, i, option_name);
510 parseSpinOption(payload, i, option_name);
514 parseComboOption(payload, i, option_name);
522 inline void UCIParser<Move>::parseCheckOption(Tokens& payload,
size_t& i,
const std::string& option_id)
524 if (i + 1 >= payload.size() || payload[i++] !=
"default") {
525 handler->onError(
"Engine send option command, but check option is missing a default value");
529 if (payload[i] !=
"true" && payload[i] !=
"false") {
530 handler->onError(
"Checkbox option value could be either true or false");
534 handler->onOption(Option(option_id, payload[i] ==
"true"));
538 inline void UCIParser<Move>::parseStringOption(Tokens& payload,
size_t& i,
const std::string& option_id)
540 std::string str_value;
541 if (i + 1 >= payload.size() || payload[i++] !=
"default") {
542 handler->onError(
"Engine send option command, but string option is missing a default value (use '<empty>' if the default is empty string");
546 while (i < payload.size())
547 str_value += payload[i++] +
" ";
549 str_value.pop_back();
551 if (str_value ==
"<empty>") str_value =
"";
554 handler->onOption(Option(option_id, str_value));
558 inline void UCIParser<Move>::parseSpinOption(Tokens& payload,
size_t& i,
const std::string& option_id)
562 int32_t min_, max_, default_;
565 while (i < payload.size()) {
570 CHECK_IF_HAS_NEXT_TOKEN(
"Spin option needs min value");
573 if ((std::stringstream(payload[i++]) >> min_).fail()) {
574 logError(
"Could not read " + payload[i - 1] +
" as integer");
580 CHECK_IF_HAS_NEXT_TOKEN(
"Spin option needs max value");
583 if ((std::stringstream(payload[i++]) >> max_).fail()) {
584 logError(
"Could not read " + payload[i - 1] +
" as integer");
589 if (temp ==
"default") {
590 CHECK_IF_HAS_NEXT_TOKEN(
"Spin option needs default value");
593 if ((std::stringstream(payload[i++]) >> default_).fail()) {
594 logError(
"Could not read " + payload[i - 1] +
" as integer");
601 logError(
"Engine send option command, but the passed spin option misses one of the required fields (min, max or default)");
605 handler->onOption(Option(option_id, spin_option({ default_, min_, max_ })));
609 inline void UCIParser<Move>::parseComboOption(Tokens& payload,
size_t& i,
const std::string& option_id)
611 std::string temp, combo_default;
612 std::vector<std::string> combo_values;
614 while (i < payload.size()) {
619 CHECK_IF_HAS_NEXT_TOKEN(
"Missing value after 'var' token");
620 combo_values.push_back(payload[i++]);
624 if (temp ==
"default") {
625 CHECK_IF_HAS_NEXT_TOKEN(
"Missing value after 'default' token");
626 combo_default = payload[i++];
630 handler->onOption(Option(option_id, combo_option({ combo_default, combo_values })));
634 inline void UCIParser<Move>::onInfo(Tokens& payload)
636 std::vector<Info<Move>> result;
638 std::function<std::function<Info<Move>(Tokens&,
size_t&)>(std::function<Info<Move>(
const std::vector<std::string>&,
size_t&, PatternMatcher&, Marschaler<Move>&)>)> decorator =
639 [
this](std::function<Info<Move>(Tokens&,
size_t&, PatternMatcher&, Marschaler<Move>&)> wrapee) {
640 return [
this, wrapee](Tokens& payload,
size_t& index) {
641 return wrapee(payload, index, *this->moveValidator, *this->moveMarshaler);
645 std::map<std::string, std::function<Info<Move>(
const std::vector<std::string>&,
size_t&)>> subparsers = {
646 {
"depth", _UCI_info_parsers::parseDepthInfo<Move>},
647 {
"seldepth", _UCI_info_parsers::parseSeldepthInfo<Move>},
648 {
"time", _UCI_info_parsers::parseTimeInfo<Move>},
649 {
"nodes", _UCI_info_parsers::parseNodesInfo<Move>},
650 {
"multipv", _UCI_info_parsers::parseMultiPvInfo<Move>},
651 {
"currmovenumber", _UCI_info_parsers::parseCurrentMoveNumberInfo<Move>},
652 {
"hashfull", _UCI_info_parsers::parseHashfullInfo<Move>},
653 {
"nps", _UCI_info_parsers::parseNodesPerSecondInfo<Move>},
654 {
"tbhits", _UCI_info_parsers::parseTbhithsInfo<Move>},
655 {
"sbhits", _UCI_info_parsers::parseSbhitsInfo<Move>},
656 {
"cpuload", _UCI_info_parsers::parseCPUloadInfo<Move>},
657 {
"string", _UCI_info_parsers::parseStringInfo<Move>},
658 {
"pv", decorator(_UCI_info_parsers::parsePvInfo<Move>)},
659 {
"refutation", decorator(_UCI_info_parsers::parseRefutationInfo<Move>)},
660 {
"currline", decorator(_UCI_info_parsers::parseCurrlineInfo<Move>)},
661 {
"currmove", decorator(_UCI_info_parsers::parseCurrentMoveInfo<Move>)},
662 {
"score", _UCI_info_parsers::parseScoreInfo<Move>}
667 auto it = subparsers.end();
669 while (i < payload.size()) {
671 it = subparsers.find(temp);
673 if (it != subparsers.end()) {
675 Info<Move> val = it->second(payload, i);
676 result.push_back(val);
678 catch (_UCI_info_parsers::UciParsingException e) {
679 logError(
"Exception occurred during executing one of info command's subparsers");
684 handler->onInfo(result);
687 template<
class Target>
690 loadInto(token, result);
694#undef CHECK_IF_HAS_NEXT_TOKEN
virtual void loadInto(const std::string &token, Target &target) const =0
virtual Target marshal(const std::string &token) const
Definition: Parser.h:688