00001
00002
00003
00004
00005
00006 #include "GtpEngine.h"
00007
00008 #include <iomanip>
00009 #include <cassert>
00010 #include <cctype>
00011 #include <fstream>
00012
00013 #if GTPENGINE_PONDER || GTPENGINE_INTERRUPT
00014 #include <boost/thread/barrier.hpp>
00015 #include <boost/thread/condition.hpp>
00016 #include <boost/thread/mutex.hpp>
00017 #include <boost/thread/thread.hpp>
00018 #include <boost/thread/xtime.hpp>
00019
00020 using boost::barrier;
00021 using boost::condition;
00022 using boost::mutex;
00023 using boost::thread;
00024 using boost::xtime;
00025 using boost::xtime_get;
00026 #endif
00027
00028 using namespace std;
00029
00030 #ifdef WIN32
00031
00032
00033
00034
00035 #pragma warning(4:4355)
00036 #endif
00037
00038
00039
00040
00041 namespace {
00042
00043 void Trim(string& str);
00044
00045
00046
00047
00048
00049 bool IsCommandLine(const string& line)
00050 {
00051 string trimmedLine = line;
00052 Trim(trimmedLine);
00053 return (! trimmedLine.empty() && trimmedLine[0] != '#');
00054 }
00055
00056 #if ! GTPENGINE_INTERRUPT
00057
00058
00059
00060
00061
00062 bool ReadCommand(GtpCommand& cmd, GtpInputStream& in)
00063 {
00064 string line;
00065 while (in.GetLine(line) && ! IsCommandLine(line))
00066 {
00067 }
00068 if (in.EndOfInput())
00069 return false;
00070 Trim(line);
00071 cmd.Init(line);
00072 return true;
00073 }
00074 #endif
00075
00076
00077
00078
00079
00080
00081 string ReplaceEmptyLines(const string& text)
00082 {
00083 if (text.find("\n\n") == string::npos)
00084 return text;
00085 istringstream in(text);
00086 ostringstream result;
00087 bool lastWasNewLine = false;
00088 char c;
00089 while (in.get(c))
00090 {
00091 bool isNewLine = (c == '\n');
00092 if (isNewLine && lastWasNewLine)
00093 result.put(' ');
00094 result.put(c);
00095 lastWasNewLine = isNewLine;
00096 }
00097 return result.str();
00098 }
00099
00100
00101
00102
00103 void Trim(string& str)
00104 {
00105 char const* whiteSpace = " \t\r";
00106 size_t pos = str.find_first_not_of(whiteSpace);
00107 str.erase(0, pos);
00108 pos = str.find_last_not_of(whiteSpace);
00109 str.erase(pos + 1);
00110 }
00111
00112 }
00113
00114
00115
00116 #if GTPENGINE_PONDER || GTPENGINE_INTERRUPT
00117
00118
00119 namespace {
00120
00121 void Notify(mutex& aMutex, condition& aCondition)
00122 {
00123 mutex::scoped_lock lock(aMutex);
00124 aCondition.notify_all();
00125 }
00126
00127 }
00128
00129 #endif // GTPENGINE_PONDER || GTPENGINE_INTERRUPT
00130
00131
00132
00133 #if GTPENGINE_PONDER
00134
00135 namespace {
00136
00137
00138
00139
00140
00141 class PonderThread
00142 {
00143 public:
00144 PonderThread(GtpEngine& engine);
00145
00146 void StartPonder();
00147
00148 void StopPonder();
00149
00150 void Quit();
00151
00152 private:
00153 class Function
00154 {
00155 public:
00156 Function(PonderThread& ponderThread);
00157
00158 void operator()();
00159
00160 private:
00161 PonderThread& m_ponderThread;
00162 };
00163
00164 friend class PonderThread::Function;
00165
00166 GtpEngine& m_engine;
00167
00168 barrier m_threadReady;
00169
00170 mutex m_startPonderMutex;
00171
00172 mutex m_ponderFinishedMutex;
00173
00174 condition m_startPonder;
00175
00176 condition m_ponderFinished;
00177
00178 mutex::scoped_lock m_ponderFinishedLock;
00179
00180
00181
00182
00183 boost::thread m_thread;
00184 };
00185
00186 PonderThread::Function::Function(PonderThread& ponderThread)
00187 : m_ponderThread(ponderThread)
00188 {
00189 }
00190
00191 void PonderThread::Function::operator()()
00192 {
00193 mutex::scoped_lock lock(m_ponderThread.m_startPonderMutex);
00194 m_ponderThread.m_threadReady.wait();
00195 while (true)
00196 {
00197 m_ponderThread.m_startPonder.wait(lock);
00198 GtpEngine& engine = m_ponderThread.m_engine;
00199 if (engine.IsQuitSet())
00200 return;
00201 engine.Ponder();
00202 Notify(m_ponderThread.m_ponderFinishedMutex,
00203 m_ponderThread.m_ponderFinished);
00204 }
00205 }
00206
00207 PonderThread::PonderThread(GtpEngine& engine)
00208 : m_engine(engine),
00209 m_threadReady(2),
00210 m_ponderFinishedLock(m_ponderFinishedMutex),
00211 m_thread(Function(*this))
00212 {
00213 m_threadReady.wait();
00214 }
00215
00216 void PonderThread::StartPonder()
00217 {
00218 m_engine.InitPonder();
00219 Notify(m_startPonderMutex, m_startPonder);
00220 }
00221
00222 void PonderThread::StopPonder()
00223 {
00224 m_engine.StopPonder();
00225 m_ponderFinished.wait(m_ponderFinishedLock);
00226 }
00227
00228 void PonderThread::Quit()
00229 {
00230 Notify(m_startPonderMutex, m_startPonder);
00231 m_thread.join();
00232 }
00233
00234 }
00235
00236 #endif // GTPENGINE_PONDER
00237
00238
00239
00240 #if GTPENGINE_INTERRUPT
00241
00242 namespace {
00243
00244
00245
00246
00247
00248
00249 class ReadThread
00250 {
00251 public:
00252 ReadThread(GtpInputStream& in, GtpEngine& engine);
00253
00254 bool ReadCommand(GtpCommand& cmd);
00255
00256 void JoinThread();
00257
00258 private:
00259 class Function
00260 {
00261 public:
00262 Function(ReadThread& readThread);
00263
00264 void operator()();
00265
00266 private:
00267 ReadThread& m_readThread;
00268
00269 void ExecuteSleepLine(const string& line);
00270 };
00271
00272 friend class ReadThread::Function;
00273
00274 GtpInputStream& m_in;
00275
00276 GtpEngine& m_engine;
00277
00278 string m_line;
00279
00280 bool m_isStreamGood;
00281
00282 barrier m_threadReady;
00283
00284 mutex m_waitCommandMutex;
00285
00286 condition m_waitCommand;
00287
00288 mutex m_commandReceivedMutex;
00289
00290 condition m_commandReceived;
00291
00292 mutex::scoped_lock m_commandReceivedLock;
00293
00294
00295
00296
00297 boost::thread m_thread;
00298 };
00299
00300 ReadThread::Function::Function(ReadThread& readThread)
00301 : m_readThread(readThread)
00302 {
00303 }
00304
00305 void ReadThread::Function::operator()()
00306 {
00307 mutex::scoped_lock lock(m_readThread.m_waitCommandMutex);
00308 m_readThread.m_threadReady.wait();
00309 GtpEngine& engine = m_readThread.m_engine;
00310 GtpInputStream& in = m_readThread.m_in;
00311 string line;
00312 while (true)
00313 {
00314 while (in.GetLine(line))
00315 {
00316 Trim(line);
00317 if (line == "# interrupt")
00318 engine.Interrupt();
00319 else if (line.find("# gtpengine-sleep ") == 0)
00320 ExecuteSleepLine(line);
00321 else if (IsCommandLine(line))
00322 break;
00323 }
00324 m_readThread.m_waitCommand.wait(lock);
00325 m_readThread.m_isStreamGood = ! in.EndOfInput();
00326 m_readThread.m_line = line;
00327 Notify(m_readThread.m_commandReceivedMutex,
00328 m_readThread.m_commandReceived);
00329 if (in.EndOfInput())
00330 return;
00331
00332 GtpCommand cmd(line);
00333 if (cmd.Name() == "quit")
00334 return;
00335 }
00336 }
00337
00338 void ReadThread::Function::ExecuteSleepLine(const string& line)
00339 {
00340 istringstream buffer(line);
00341 string s;
00342 buffer >> s;
00343 assert(s == "#");
00344 buffer >> s;
00345 assert(s == "gtpengine-sleep");
00346 int seconds;
00347 buffer >> seconds;
00348 if (seconds > 0)
00349 {
00350 cerr << "GtpEngine: sleep " << seconds << '\n';
00351 xtime time;
00352 xtime_get(&time, boost::TIME_UTC);
00353 time.sec += seconds;
00354 thread::sleep(time);
00355 cerr << "GtpEngine: sleep done\n";
00356 }
00357 }
00358
00359 void ReadThread::JoinThread()
00360 {
00361 m_thread.join();
00362 }
00363
00364 ReadThread::ReadThread(GtpInputStream& in, GtpEngine& engine)
00365 : m_in(in),
00366 m_engine(engine),
00367 m_threadReady(2),
00368 m_commandReceivedLock(m_commandReceivedMutex),
00369 m_thread(Function(*this))
00370 {
00371 m_threadReady.wait();
00372 }
00373
00374 bool ReadThread::ReadCommand(GtpCommand& cmd)
00375 {
00376 Notify(m_waitCommandMutex, m_waitCommand);
00377 m_commandReceived.wait(m_commandReceivedLock);
00378 if (! m_isStreamGood)
00379 return false;
00380 cmd.Init(m_line);
00381 return true;
00382 }
00383
00384 }
00385
00386 #endif // GTPENGINE_INTERRUPT
00387
00388
00389
00390 GtpFailure::GtpFailure()
00391 {
00392 }
00393
00394 GtpFailure::GtpFailure(const GtpFailure& failure)
00395 {
00396 m_response << failure.Response();
00397 m_response.copyfmt(failure.m_response);
00398 }
00399
00400 GtpFailure::GtpFailure(const string& response)
00401 {
00402 m_response << response;
00403 }
00404
00405 GtpFailure::~GtpFailure() throw()
00406 {
00407 }
00408
00409
00410
00411 GtpCommand::Argument::Argument(const string& value, std::size_t end)
00412 : m_value(value),
00413 m_end(end)
00414 {
00415 }
00416
00417
00418
00419 ostringstream GtpCommand::s_dummy;
00420
00421 const string& GtpCommand::Arg(std::size_t number) const
00422 {
00423 size_t index = number + 1;
00424 if (number >= NuArg())
00425 throw GtpFailure() << "missing argument " << index;
00426 return m_arguments[index].m_value;
00427 }
00428
00429 const string& GtpCommand::Arg() const
00430 {
00431 CheckNuArg(1);
00432 return Arg(0);
00433 }
00434
00435 template<>
00436 std::size_t GtpCommand::Arg<std::size_t>(std::size_t i) const
00437 {
00438
00439
00440 string arg = Arg(i);
00441 bool fail = (! arg.empty() && arg[0] == '-');
00442 size_t result;
00443 if (! fail)
00444 {
00445 istringstream in(arg);
00446 in >> result;
00447 fail = ! in;
00448 }
00449 if (fail)
00450 throw GtpFailure() << "argument " << (i + 1) << " (" << arg
00451 << ") must be of type size_t";
00452 return result;
00453 }
00454
00455 std::string GtpCommand::ArgLine() const
00456 {
00457 string result = m_line.substr(m_arguments[0].m_end);
00458 Trim(result);
00459 return result;
00460 }
00461
00462 string GtpCommand::ArgToLower(std::size_t number) const
00463 {
00464 string value = Arg(number);
00465 for (size_t i = 0; i < value.length(); ++i)
00466 value[i] = char(tolower(value[i]));
00467 return value;
00468 }
00469
00470 bool GtpCommand::BoolArg(std::size_t number) const
00471 {
00472 return Arg<bool>(number);
00473 }
00474
00475 void GtpCommand::CheckNuArg(std::size_t number) const
00476 {
00477 if (NuArg() == number)
00478 return;
00479 if (number == 0)
00480 throw GtpFailure() << "no arguments allowed";
00481 else if (number == 1)
00482 throw GtpFailure() << "command needs one argument";
00483 else
00484 throw GtpFailure() << "command needs " << number << " arguments";
00485 }
00486
00487 void GtpCommand::CheckNuArgLessEqual(std::size_t number) const
00488 {
00489 if (NuArg() <= number)
00490 return;
00491 if (number == 1)
00492 throw GtpFailure() << "command needs at most one argument";
00493 else
00494 throw GtpFailure() << "command needs at most " << number << " arguments";
00495 }
00496
00497 double GtpCommand::FloatArg(std::size_t number) const
00498 {
00499 return Arg<double>(number);
00500 }
00501
00502 int GtpCommand::IntArg(std::size_t number) const
00503 {
00504 return Arg<int>(number);
00505 }
00506
00507 int GtpCommand::IntArg(std::size_t number, int min) const
00508 {
00509 return ArgMin<int>(number, min);
00510 }
00511
00512 int GtpCommand::IntArg(std::size_t number, int min, int max) const
00513 {
00514 return ArgMinMax<int>(number, min, max);
00515 }
00516
00517 void GtpCommand::Init(const string& line)
00518 {
00519 m_line = line;
00520 Trim(m_line);
00521 SplitLine(m_line);
00522 assert(m_arguments.size() > 0);
00523 ParseCommandId();
00524 assert(m_arguments.size() > 0);
00525 m_response.str("");
00526 m_response.copyfmt(s_dummy);
00527 }
00528
00529 void GtpCommand::ParseCommandId()
00530 {
00531 m_id = "";
00532 if (m_arguments.size() < 2)
00533 return;
00534 istringstream in(m_arguments[0].m_value);
00535 int id;
00536 in >> id;
00537 if (in)
00538 {
00539 m_id = m_arguments[0].m_value;
00540 m_arguments.erase(m_arguments.begin());
00541 }
00542 }
00543
00544 string GtpCommand::RemainingLine(std::size_t number) const
00545 {
00546 size_t index = number + 1;
00547 if (number >= NuArg())
00548 throw GtpFailure() << "missing argument " << index;
00549 string result = m_line.substr(m_arguments[index].m_end);
00550 Trim(result);
00551 return result;
00552 }
00553
00554 void GtpCommand::SetResponse(const string& response)
00555 {
00556 m_response.str(response);
00557 }
00558
00559 void GtpCommand::SetResponseBool(bool value)
00560 {
00561 m_response.str(value ? "true" : "false");
00562 }
00563
00564 std::size_t GtpCommand::SizeTypeArg(std::size_t number) const
00565 {
00566 return Arg<size_t>(number);
00567 }
00568
00569 std::size_t GtpCommand::SizeTypeArg(std::size_t number, std::size_t min) const
00570 {
00571 return ArgMin<size_t>(number, min);
00572 }
00573
00574
00575
00576
00577
00578
00579 void GtpCommand::SplitLine(const string& line)
00580 {
00581 m_arguments.clear();
00582 bool escape = false;
00583 bool inString = false;
00584 ostringstream element;
00585 for (size_t i = 0; i < line.size(); ++i)
00586 {
00587 char c = line[i];
00588 if (c == '"' && ! escape)
00589 {
00590 if (inString)
00591 {
00592 m_arguments.push_back(Argument(element.str(), i + 1));
00593 element.str("");
00594 }
00595 inString = ! inString;
00596 }
00597 else if (isspace(c) && ! inString)
00598 {
00599 if (! element.str().empty())
00600 {
00601 m_arguments.push_back(Argument(element.str(), i + 1));
00602 element.str("");
00603 }
00604 }
00605 else
00606 element << c;
00607 escape = (c == '\\' && ! escape);
00608 }
00609 if (! element.str().empty())
00610 m_arguments.push_back(Argument(element.str(), line.size()));
00611 }
00612
00613
00614
00615 GtpCallbackBase::~GtpCallbackBase() throw()
00616 {
00617 }
00618
00619
00620
00621 GtpEngine::GtpEngine()
00622 : m_quit(false)
00623 {
00624 Register("known_command", &GtpEngine::CmdKnownCommand, this);
00625 Register("list_commands", &GtpEngine::CmdListCommands, this);
00626 Register("name", &GtpEngine::CmdName, this);
00627 Register("protocol_version", &GtpEngine::CmdProtocolVersion, this);
00628 Register("quit", &GtpEngine::CmdQuit, this);
00629 Register("version", &GtpEngine::CmdVersion, this);
00630 }
00631
00632 GtpEngine::~GtpEngine()
00633 {
00634 typedef CallbackMap::iterator Iterator;
00635 for (Iterator i = m_callbacks.begin(); i != m_callbacks.end(); ++i)
00636 {
00637 delete i->second;
00638 #ifndef NDEBUG
00639 i->second = 0;
00640 #endif
00641 }
00642 }
00643
00644 void GtpEngine::BeforeHandleCommand()
00645 {
00646
00647 }
00648
00649 void GtpEngine::BeforeWritingResponse()
00650 {
00651
00652 }
00653
00654
00655 void GtpEngine::CmdKnownCommand(GtpCommand& cmd)
00656 {
00657 cmd.SetResponseBool(IsRegistered(cmd.Arg()));
00658 }
00659
00660
00661 void GtpEngine::CmdListCommands(GtpCommand& cmd)
00662 {
00663 cmd.CheckArgNone();
00664 typedef CallbackMap::const_iterator Iterator;
00665 for (Iterator i = m_callbacks.begin(); i != m_callbacks.end(); ++i)
00666 cmd << i->first << '\n';
00667 }
00668
00669
00670 void GtpEngine::CmdName(GtpCommand& cmd)
00671 {
00672 cmd.CheckArgNone();
00673 cmd << "Unknown";
00674 }
00675
00676
00677 void GtpEngine::CmdProtocolVersion(GtpCommand& cmd)
00678 {
00679 cmd.CheckArgNone();
00680 cmd << "2";
00681 }
00682
00683
00684 void GtpEngine::CmdQuit(GtpCommand& cmd)
00685 {
00686 cmd.CheckArgNone();
00687 SetQuit();
00688 }
00689
00690
00691
00692
00693 void GtpEngine::CmdVersion(GtpCommand& cmd)
00694 {
00695 cmd.CheckArgNone();
00696 }
00697
00698 string GtpEngine::ExecuteCommand(const string& cmdline, ostream& log)
00699 {
00700 if (! IsCommandLine(cmdline))
00701 throw GtpFailure() << "Bad command: " << cmdline;
00702 GtpCommand cmd;
00703 cmd.Init(cmdline);
00704 log << cmd.Line() << '\n';
00705 GtpOutputStream gtpLog(log);
00706 bool status = HandleCommand(cmd, gtpLog);
00707 string response = cmd.Response();
00708 if (! status)
00709 throw GtpFailure() << "Executing " << cmd.Line() << " failed";
00710 return response;
00711 }
00712
00713 void GtpEngine::ExecuteFile(const string& name, ostream& log)
00714 {
00715 ifstream in(name.c_str());
00716 if (! in)
00717 throw GtpFailure() << "Cannot read " << name;
00718 string line;
00719 GtpCommand cmd;
00720 GtpOutputStream gtpLog(log);
00721 while (getline(in, line))
00722 {
00723 if (! IsCommandLine(line))
00724 continue;
00725 cmd.Init(line);
00726 log << cmd.Line() << '\n';
00727
00728 bool status = HandleCommand(cmd, gtpLog);
00729 if (! status)
00730 throw GtpFailure() << "Executing " << cmd.Line() << " failed";
00731 }
00732 }
00733
00734 bool GtpEngine::HandleCommand(GtpCommand& cmd, GtpOutputStream& out)
00735 {
00736 BeforeHandleCommand();
00737 bool status = true;
00738 string response;
00739 try
00740 {
00741 CallbackMap::const_iterator pos = m_callbacks.find(cmd.Name());
00742 if (pos == m_callbacks.end())
00743 {
00744 status = false;
00745 response = "unknown command: " + cmd.Name();
00746 }
00747 else
00748 {
00749 GtpCallbackBase* callback = pos->second;
00750 (*callback)(cmd);
00751 response = cmd.Response();
00752 }
00753 }
00754 catch (const GtpFailure& failure)
00755 {
00756 status = false;
00757 response = failure.Response();
00758 }
00759 response = ReplaceEmptyLines(response);
00760 BeforeWritingResponse();
00761 ostringstream ostr;
00762 ostr << (status ? '=' : '?') << cmd.ID() << ' ' << response;
00763 size_t size = response.size();
00764 if (size == 0 || response[size - 1] != '\n')
00765 ostr << '\n';
00766 ostr << '\n' << flush;
00767 out.Write(ostr.str());
00768 out.Flush();
00769 return status;
00770 }
00771
00772 bool GtpEngine::IsRegistered(const string& command) const
00773 {
00774 return (m_callbacks.find(command) != m_callbacks.end());
00775 }
00776
00777 void GtpEngine::MainLoop(GtpInputStream& in, GtpOutputStream& out)
00778 {
00779 m_quit = false;
00780 #if GTPENGINE_PONDER
00781 PonderThread ponderThread(*this);
00782 #endif
00783 #if GTPENGINE_INTERRUPT
00784 ReadThread readThread(in, *this);
00785 #endif
00786 GtpCommand cmd;
00787 while (true)
00788 {
00789 #if GTPENGINE_PONDER
00790 ponderThread.StartPonder();
00791 #endif
00792 #if GTPENGINE_INTERRUPT
00793 bool isStreamGood = readThread.ReadCommand(cmd);
00794 #else
00795 bool isStreamGood = ReadCommand(cmd, in);
00796 #endif
00797 #if GTPENGINE_PONDER
00798 ponderThread.StopPonder();
00799 #endif
00800 if (isStreamGood)
00801 HandleCommand(cmd, out);
00802 else
00803 SetQuit();
00804 if (m_quit)
00805 {
00806 #if GTPENGINE_PONDER
00807 ponderThread.Quit();
00808 #endif
00809 #if GTPENGINE_INTERRUPT
00810 readThread.JoinThread();
00811 #endif
00812 break;
00813 }
00814 }
00815 }
00816
00817 void GtpEngine::Register(const string& command, GtpCallbackBase* callback)
00818 {
00819 CallbackMap::iterator pos = m_callbacks.find(command);
00820 if (pos != m_callbacks.end())
00821 {
00822 delete pos->second;
00823 #ifndef NDEBUG
00824 pos->second = 0;
00825 #endif
00826 m_callbacks.erase(pos);
00827 }
00828 m_callbacks.insert(make_pair(command, callback));
00829 }
00830
00831 void GtpEngine::SetQuit()
00832 {
00833 m_quit = true;
00834 }
00835
00836 bool GtpEngine::IsQuitSet() const
00837 {
00838 return m_quit;
00839 }
00840
00841
00842 #if GTPENGINE_PONDER
00843
00844 void GtpEngine::Ponder()
00845 {
00846
00847 #ifdef GTPENGINE_TEST
00848 cerr << "GtpEngine::Ponder()\n";
00849 #endif
00850 }
00851
00852 void GtpEngine::StopPonder()
00853 {
00854
00855 #ifdef GTPENGINE_TEST
00856 cerr << "GtpEngine::StopPonder()\n";
00857 #endif
00858 }
00859
00860 void GtpEngine::InitPonder()
00861 {
00862
00863 #ifdef GTPENGINE_TEST
00864 cerr << "GtpEngine::InitPonder()\n";
00865 #endif
00866 }
00867
00868 #endif // GTPENGINE_PONDER
00869
00870
00871 #if GTPENGINE_INTERRUPT
00872
00873 void GtpEngine::Interrupt()
00874 {
00875
00876 #ifdef GTPENGINE_TEST
00877 cerr << "GtpEngine::Interrupt()\n";
00878 #endif
00879 }
00880
00881 #endif // GTPENGINE_INTERRUPT
00882
00883
00884
00885 #ifdef GTPENGINE_MAIN
00886
00887
00888 int main()
00889 {
00890 try
00891 {
00892 GtpEngine engine(cin, cout);
00893 engine.MainLoop();
00894 }
00895 catch (const exception& e)
00896 {
00897 cerr << e.what() << '\n';
00898 return 1;
00899 }
00900 return 0;
00901 }
00902
00903 #endif // GTPENGINE_MAIN
00904
00905