UCILoader 1.1.2
Small C++ library that allows user to connect to a chess engines via UCI protocol.
Loading...
Searching...
No Matches
Parser.h
1#pragma once
2
3#include "UCI.h"
4
5#include <functional>
6#include <sstream>
7#include <memory>
8#include <map>
9
10namespace UCILoader {
11
16 public:
17 virtual bool match(const std::string& val) const = 0;
18 };
19
20 class StandardChessMoveMatcher : public PatternMatcher {
21 public:
22 bool match(const std::string& val) const;
23 };
24
33 template<class Target>
34 class Marschaler {
35 public:
39 virtual void loadInto(const std::string& token, Target& target) const = 0;
40
44 virtual Target marshal(const std::string& token) const;
45 };
46
47
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);}
50
51 namespace _UCI_info_parsers {
52
53 class UciParsingException : public std::exception {};
54
55 template <class Move>
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();
59 }
60
61 std::string value_string = tokens[i++];
62 int32_t value;
63
64 if ((std::stringstream(value_string) >> value).fail()) {
65 throw UciParsingException();
66 }
67
68 return factoryMethod(value);
69 }
70
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);
82
83 template <class Move>
84 Info<Move> parseStringInfo(const std::vector<std::string>& tokens, size_t& i) {
85
86 if (i >= tokens.size()) {
87 throw UciParsingException();
88 }
89
90 std::string accumulator = tokens[i++];
91 while (i < tokens.size())
92 accumulator += ' ' + tokens[i++];
93
94 return InfoFactory<Move>::makeStringInfo(accumulator);
95 }
96
97 template <class Move>
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]))
102 break;
103
104 result.push_back(marshaler.marshal(tokens[i++]));
105 }
106
107 return InfoFactory<Move>::makePvInfo(result);
108 }
109
110 template <class Move>
111 Info <Move> parseRefutationInfo(const std::vector<std::string>& tokens, size_t& i, PatternMatcher& movePatternMatcher, Marschaler<Move>& marshaler) {
112 Move m;
113 std::vector<Move> result;
114
115 if (i >= tokens.size() || !movePatternMatcher.match(tokens[i]))
116 throw UciParsingException();
117
118 marshaler.loadInto(tokens[i++], m);
119
120 while (i < tokens.size()) {
121 if (!movePatternMatcher.match(tokens[i]))
122 break;
123
124 result.push_back(marshaler.marshal(tokens[i++]));
125 }
126
127 return InfoFactory<Move>::makeRefutationInfo(m, result);
128 }
129
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;
133
134 if (i >= tokens.size())
135 throw UciParsingException();
136
137 std::string first = tokens[i];
138 int32_t cpunr = 1;
139
140 if ((std::stringstream(first) >> cpunr).fail())
141 cpunr = 1;
142 else
143 ++i;
144
145
146 while (i < tokens.size()) {
147 if (!movePatternMatcher.match(tokens[i]))
148 break;
149
150 result.push_back(marshaler.marshal(tokens[i++]));
151 }
152
153 return InfoFactory<Move>::makeCurrLineInfo(result, cpunr);
154 }
155
156 template <class Move>
157 Info<Move> parseCurrentMoveInfo(const std::vector<std::string>& tokens, size_t& i, PatternMatcher& movePatternMatcher, Marschaler<Move>& marshaler) {
158 Move result;
159 if (i >= tokens.size() || !movePatternMatcher.match(tokens[i]))
160 throw UciParsingException();
161
162 marshaler.loadInto(tokens[i++], result);
163 return InfoFactory<Move>::makeCurrentMoveInfo(result);
164 }
165
166 template <class Move>
167 Info<Move> parseScoreInfo(const std::vector<std::string>& tokens, size_t& i) {
168
169 std::string type = "", temp;
170 UciScore::BoundType _boundType = UciScore::Exact;
171 int32_t eval;
172 while (i < tokens.size()) {
173 temp = tokens[i++];
174
175 if (temp == "cp" || temp == "mate") {
176 type = temp;
177 if (i >= tokens.size() || (std::stringstream(tokens[i++]) >> eval).fail())
178 throw UciParsingException();
179 break;
180 }
181 else if (temp == "lowerbound")
182 _boundType = UciScore::Lowerbound;
183 else if (temp == "upperbound")
184 _boundType = UciScore::Upperbound;
185 }
186
187
188
189 if (type != "cp" && type != "mate")
190 throw UciParsingException();
191
192
193 UciScore score = (type[0] == 'c' ? UciScore::fromCentipawns : UciScore::fromMateDistance)(eval, _boundType);
194 return InfoFactory<Move>::makeScoreInfo(score);
195 }
196
197 };
198
199#undef _INTEGER_OPTION_PARSER
200
201
202#define __RECEIVE_SIGNAL(SIGNAL) [this](Tokens & in) {this->on##SIGNAL(in);}
203 template<class Move>
204 class UCIParser {
205 typedef const std::vector<std::string> Tokens;
206 std::shared_ptr<AbstractEngineHandler<Move>> handler;
207
208 std::shared_ptr<Marschaler<Move>> moveMarshaler;
209 std::shared_ptr<PatternMatcher> moveValidator;
210
211 std::map<std::string, std::function<void(Tokens&)>> router;
212
213
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);
222
223
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);
228
229 void logError(const std::string& errorMsg);
230
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);
234
235 std::vector<std::string> tokenize(const std::string& line) const;
236 public:
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) {
239 router = {
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)}
248 };
249
250 };
251 void parseLine(const std::string& line);
252
253 };
254
255#undef __RECEIVE_SIGNAL
256
257#define CHECK_IF_HAS_NEXT_TOKEN(msg) if (i >= payload.size()) { handler->onError(msg); return; }
258
259
260 template<class Move>
261 inline void UCIParser<Move>::onRegistration(Tokens& payload)
262 {
263 // expected format is registration [ok/error/checking]
264
265 if (payload.size() < 2) {
266 logError(notEnoughArguments(2, payload));
267 return;
268 };
269
270
271 const std::string& commandStatus = payload[1];
272
273 if (commandStatus == "ok") {
274 handler->onRegistration(ProcedureStatus::Ok);
275 return;
276 }
277
278
279 if (commandStatus == "error") {
280 handler->onRegistration(ProcedureStatus::Error);
281 return;
282 }
283
284 if (commandStatus == "checking") {
285 handler->onRegistration(ProcedureStatus::Checking);
286 return;
287 }
288
289 logError(notExpectedFormat("registration [ok/checking/error]", payload));
290 }
291
292 template<class Move>
293 inline void UCIParser<Move>::onCopyprotection(Tokens& payload)
294 {
295 // expected format is copyprotection [ok/error/checking]
296
297 if (payload.size() < 2) {
298 logError(notEnoughArguments(2, payload));
299 return;
300 };
301
302 const std::string& commandStatus = payload[1];
303
304 if (commandStatus == "ok") {
305 handler->onCopyProtection(ProcedureStatus::Ok);
306 return;
307 }
308
309
310 if (commandStatus == "error") {
311 handler->onCopyProtection(ProcedureStatus::Error);
312 return;
313 }
314
315 if (commandStatus == "checking") {
316 handler->onCopyProtection(ProcedureStatus::Checking);
317 return;
318 }
319
320 logError(notExpectedFormat("copyprotection [ok/checking/error]", payload));
321 }
322
323 template<class Move>
324 inline void UCIParser<Move>::logError(const std::string& errorMsg)
325 {
326 handler->onError(errorMsg);
327 }
328
329 template<class Move>
330 inline std::string UCIParser<Move>::concatTokens(Tokens& payload, size_t start, char sep) const {
331
332 std::string result = payload[start];
333
334 for (size_t i = start + 1; i < payload.size(); ++i) {
335 result.push_back(sep);
336 result += payload[i];
337 }
338
339 return result;
340 }
341
342 template<class Move>
343 inline std::string UCIParser<Move>::notEnoughArguments(size_t expectedArgumentCount, Tokens& payload)
344 {
345 return std::string("Error! Command ") + payload[0] + " expects " + std::to_string(expectedArgumentCount);
346 }
347
348 template<class Move>
349 inline std::string UCIParser<Move>::notExpectedFormat(const std::string& expected, Tokens& found)
350 {
351 return std::string("Format expected: ") + expected + ", found:" + concatTokens(found, 0, ' ');
352 }
353
354 template<class Move>
355 inline std::vector<std::string> UCIParser<Move>::tokenize(const std::string& line) const
356 {
357 std::vector<std::string> tokens;
358 std::stringstream sstream(line);
359 std::string token;
360
361 while (sstream) {
362 token = "";
363 sstream >> token;
364 if (token.size()) tokens.push_back(token);
365 }
366
367 return tokens;
368 }
369
370 template<class Move>
371 inline void UCIParser<Move>::parseLine(const std::string& line)
372 {
373 Tokens tokens = tokenize(line);
374
375 if (tokens.size() < 1) return;
376
377 auto it = router.find(tokens[0]);
378
379 if (it == router.end()) {
380 logError(std::string("Unrecognised command: ") + tokens[0]);
381 return;
382 };
383
384 it->second(tokens);
385
386
387 }
388
389 template<class Move>
390 inline void UCIParser<Move>::onUciok(Tokens& payload)
391 {
392 handler->onUCIOK();
393 }
394
395 template<class Move>
396 inline void UCIParser<Move>::onReadyok(Tokens& payload)
397 {
398 handler->onReadyOK();
399 }
400
401 template<class Move>
402 inline void UCIParser<Move>::onID(Tokens& payload)
403 {
404 if (payload.size() < 3) {
405 logError(notEnoughArguments(3, payload));
406 return;
407 };
408
409 if (payload[1] == "name") {
410 handler->onEngineName(concatTokens(payload, 2, ' '));
411 return;
412 }
413
414 if (payload[1] == "author") {
415 handler->onEngineAuthor(concatTokens(payload, 2, ' '));
416 return;
417 }
418
419 logError(notExpectedFormat("id name/author ...", payload));
420 }
421
422 template<class Move>
423 inline void UCIParser<Move>::onBestMove(Tokens& payload)
424 {
425 // bestmove <move> ponder <move>
426
427 if (payload.size() != 2 && payload.size() != 4) {
428 logError("Incorrect argument count received for command bestmove");
429 return;
430 }
431
432 if (!moveValidator->match(payload[1])) {
433 logError("Engine send ill formatted move");
434 return;
435 }
436
437 Move bestMove = moveMarshaler->marshal(payload[1]);
438 Move ponderMove;
439
440 if (payload.size() == 4 && payload[2] == "ponder" && moveValidator->match(payload[3])) {
441 ponderMove = moveMarshaler->marshal(payload[3]);
442 handler->onBestMove(bestMove, ponderMove);
443 return;
444 }
445
446 handler->onBestMove(bestMove);
447
448 }
449
450 template<class Move>
451 inline void UCIParser<Move>::onOption(Tokens& payload)
452 {
453
454 // option name [at least 1 word of option name] value [value]
455
456 static const std::map<std::string, OptionType> _types = {
457 {"button", Button},
458 {"string", String},
459 {"check", Check},
460 {"combo", Combo},
461 {"spin", Spin}
462 };
463
464
465 if (payload.size() < 2 || payload[1] != "name") {
466 logError("Engine send option command, but 'name' token was not the 2nd argument");
467 return;
468 }
469
470 size_t i = 2;
471
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;
475
476
477 while (i < payload.size() && payload[i] != "type") {
478 option_name += payload[i++];
479 option_name += " ";
480 };
481
482 CHECK_IF_HAS_NEXT_TOKEN("Engine send option command, but name sequence was not terminated by 'type' token.");
483
484 option_name.pop_back(); // we got a trailing space after the while loop
485
486 i++; // skip the 'type' token
487
488 CHECK_IF_HAS_NEXT_TOKEN("Engine send option command, but it unexpectedly terminated after the 'type' token.")
489
490 auto option_type_it = _types.find(payload[i]);
491
492 if (option_type_it == _types.end()) {
493 handler->onError(payload[i] + " is not recognised option type.");
494 return;
495 }
496
497 ++i;
498
499 switch (option_type_it->second) {
500 case Button:
501 handler->onOption(Option(option_name)); break;
502 case Check:
503 parseCheckOption(payload, i, option_name);
504 break;
505 case String:
506 parseStringOption(payload, i, option_name);
507 break;
508
509 case Spin:
510 parseSpinOption(payload, i, option_name);
511 break;
512
513 case Combo:
514 parseComboOption(payload, i, option_name);
515 break;
516
517 }
518
519 }
520
521 template<class Move>
522 inline void UCIParser<Move>::parseCheckOption(Tokens& payload, size_t& i, const std::string& option_id)
523 {
524 if (i + 1 >= payload.size() || payload[i++] != "default") {
525 handler->onError("Engine send option command, but check option is missing a default value");
526 return;
527 }
528
529 if (payload[i] != "true" && payload[i] != "false") {
530 handler->onError("Checkbox option value could be either true or false");
531 return;
532 }
533
534 handler->onOption(Option(option_id, payload[i] == "true"));
535 }
536
537 template<class Move>
538 inline void UCIParser<Move>::parseStringOption(Tokens& payload, size_t& i, const std::string& option_id)
539 {
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");
543 return;
544 }
545
546 while (i < payload.size())
547 str_value += payload[i++] + " ";
548
549 str_value.pop_back();
550
551 if (str_value == "<empty>") str_value = "";
552
553
554 handler->onOption(Option(option_id, str_value));
555 }
556
557 template<class Move>
558 inline void UCIParser<Move>::parseSpinOption(Tokens& payload, size_t& i, const std::string& option_id)
559 {
560 std::string temp;
561
562 int32_t min_, max_, default_;
563
564 uint8_t flags = 0u;
565 while (i < payload.size()) {
566
567 temp = payload[i++];
568
569 if (temp == "min") {
570 CHECK_IF_HAS_NEXT_TOKEN("Spin option needs min value");
571 flags |= 1u;
572
573 if ((std::stringstream(payload[i++]) >> min_).fail()) {
574 logError("Could not read " + payload[i - 1] + " as integer");
575 return;
576 }
577 }
578
579 if (temp == "max") {
580 CHECK_IF_HAS_NEXT_TOKEN("Spin option needs max value");
581 flags |= 2u;
582
583 if ((std::stringstream(payload[i++]) >> max_).fail()) {
584 logError("Could not read " + payload[i - 1] + " as integer");
585 return;
586 }
587 }
588
589 if (temp == "default") {
590 CHECK_IF_HAS_NEXT_TOKEN("Spin option needs default value");
591 flags |= 4u;
592
593 if ((std::stringstream(payload[i++]) >> default_).fail()) {
594 logError("Could not read " + payload[i - 1] + " as integer");
595 return;
596 }
597 }
598 }
599
600 if (flags != 7u) {
601 logError("Engine send option command, but the passed spin option misses one of the required fields (min, max or default)");
602 return;
603 }
604
605 handler->onOption(Option(option_id, spin_option({ default_, min_, max_ })));
606 }
607
608 template<class Move>
609 inline void UCIParser<Move>::parseComboOption(Tokens& payload, size_t& i, const std::string& option_id)
610 {
611 std::string temp, combo_default;
612 std::vector<std::string> combo_values;
613
614 while (i < payload.size()) {
615
616 temp = payload[i++];
617
618 if (temp == "var") {
619 CHECK_IF_HAS_NEXT_TOKEN("Missing value after 'var' token");
620 combo_values.push_back(payload[i++]);
621 }
622
623
624 if (temp == "default") {
625 CHECK_IF_HAS_NEXT_TOKEN("Missing value after 'default' token");
626 combo_default = payload[i++];
627 }
628
629 }
630 handler->onOption(Option(option_id, combo_option({ combo_default, combo_values })));
631 }
632
633 template<class Move>
634 inline void UCIParser<Move>::onInfo(Tokens& payload)
635 {
636 std::vector<Info<Move>> result;
637
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);
642 };
643 };
644
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>}
663 };
664
665 size_t i = 1;
666 std::string temp;
667 auto it = subparsers.end();
668
669 while (i < payload.size()) {
670 temp = payload[i++];
671 it = subparsers.find(temp);
672
673 if (it != subparsers.end()) {
674 try {
675 Info<Move> val = it->second(payload, i);
676 result.push_back(val);
677 }
678 catch (_UCI_info_parsers::UciParsingException e) {
679 logError("Exception occurred during executing one of info command's subparsers");
680 }
681 }
682 }
683
684 handler->onInfo(result);
685 }
686
687 template<class Target>
688 inline Target Marschaler<Target>::marshal(const std::string& token) const {
689 Target result;
690 loadInto(token, result);
691 return result;
692 }
693
694#undef CHECK_IF_HAS_NEXT_TOKEN
695}
696
Definition: Parser.h:34
virtual void loadInto(const std::string &token, Target &target) const =0
virtual Target marshal(const std::string &token) const
Definition: Parser.h:688
Definition: Parser.h:15