14 #include <boost/program_options/options_description.hpp> 
   15 #include <boost/program_options/variables_map.hpp> 
   16 #include <boost/program_options/parsers.hpp> 
   17 #include <boost/property_tree/json_parser.hpp> 
   18 #include <boost/property_tree/ptree.hpp> 
   19 #include <boost/archive/text_oarchive.hpp> 
   20 #include <boost/archive/text_iarchive.hpp> 
   23 #include <knowrob/knowrob.h> 
   24 #include <knowrob/Logger.h> 
   25 #include <knowrob/KnowledgeBase.h> 
   26 #include "knowrob/formulas/Predicate.h" 
   27 #include "knowrob/queries/QueryParser.h" 
   28 #include "knowrob/semweb/PrefixRegistry.h" 
   29 #include "knowrob/queries/QueryError.h" 
   30 #include "knowrob/queries/QueryTree.h" 
   31 #include "knowrob/queries/Answer.h" 
   32 #include "knowrob/queries/AnswerYes.h" 
   33 #include "knowrob/queries/FormulaQuery.h" 
   34 #include "knowrob/integration/InterfaceUtils.h" 
   37 namespace po = boost::program_options;
 
   39 static const char *PROMPT = 
"?- ";
 
   44         explicit QueryHistory()
 
   45                 : selection_(data_.end()),
 
   46                   maxHistoryItems_(100),
 
   50             data_.push_front(queryString);
 
   55             selection_ = data_.end();
 
   61             std::ofstream file(tmpFileName);
 
   63                 boost::archive::text_oarchive oa(file);
 
   65                 for (
auto &x: data_) oa << x;
 
   68                 std::filesystem::rename(tmpFileName, historyFile);
 
   70                 KB_WARN(
"unable to write history to file '{}'", historyFile);
 
   75             std::ifstream file(historyFile);
 
   77                 boost::archive::text_iarchive ia(file);
 
   78                 std::list<std::string>::size_type size;
 
   80                 size = std::min(maxHistoryItems_, size);
 
   81                 for (std::size_t i = 0; i < size; ++i) {
 
   84                     data_.push_back(queryString);
 
   89         const std::string &getSelection() { 
return *selection_; }
 
   91         bool hasSelection() { 
return selection_ != data_.end(); }
 
   95                 selection_ = data_.begin();
 
   97             } 
else if (pos_ == 0) {
 
  100             if (selection_ == data_.end()) {
 
  105         void previousItem() {
 
  106             if (selection_ == data_.begin()) {
 
  107                 selection_ = data_.end();
 
  109             } 
else if (pos_ == 0 || pos_ == 1) {
 
  116         std::list<std::string> data_;
 
  117         std::list<std::string>::iterator selection_;
 
  118         unsigned long maxHistoryItems_;
 
  123     class TerminalCommand {
 
  125         using CommandFunction = 
std::function<bool(
const std::vector<T> &arguments)>;
 
  127         TerminalCommand(
std::string functor, uint32_t arity, CommandFunction 
function)
 
  128                 : functor_(std::move(functor)), arity_(arity), function_(std::move(
function)) {}
 
  130         bool runCommand(
const std::vector<T> &arguments) {
 
  131             if (arguments.size() != arity_) {
 
  132                 throw QueryError(
"Wrong number of arguments for terminal command '{}/{}'. " 
  133                                  "Actual number of arguments: {}.", functor_, arity_, arguments.size());
 
  135             return function_(arguments);
 
  140         const uint32_t arity_;
 
  141         const CommandFunction function_;
 
  145 class KnowRobTerminal {
 
  147     explicit KnowRobTerminal(
const boost::property_tree::ptree &config)
 
  149               has_stop_request_(false),
 
  152               historyFile_(
"history.txt") {
 
  154             history_.load(historyFile_);
 
  156         catch (boost::archive::archive_exception &e) {
 
  157             KB_WARN(
"A 'boost::archive' exception occurred " 
  158                     "when loading history file ({}) of the terminal: {}. " 
  159                     "It might be that the file is corrupted for some reason.",
 
  160                     historyFile_, e.what());
 
  163         registerCommand(
"exit", 0,
 
  164                         [
this](
const std::vector<TermPtr> &) { 
return exitTerminal(); });
 
  165         registerCommand(
"assert", 1,
 
  167         registerCommand(
"tell", 1,
 
  171     static char getch() {
 
  172         static struct termios old, current;
 
  178         current.c_lflag &= ~ICANON; 
 
  179         current.c_lflag &= ~ECHO;   
 
  180         tcsetattr(0, TCSANOW, ¤t);
 
  182         if (read(0, &buf, 1) < 0)
 
  185         tcsetattr(0, TCSANOW, &old);
 
  191                          const TerminalCommand<TermPtr>::CommandFunction &
function) {
 
  192         firstOrderCommands_.emplace(functor, TerminalCommand(functor, arity, 
function));
 
  197                          const TerminalCommand<FormulaPtr>::CommandFunction &
function) {
 
  198         higherOrderCommands_.emplace(functor, TerminalCommand(functor, arity, 
function));
 
  202     bool pushQueryResult(
const AnswerPtr &solution) {
 
  203         std::cout << *solution;
 
  205         return !has_stop_request_;
 
  210         auto needle = higherOrderCommands_.find(functor);
 
  211         if (needle == higherOrderCommands_.end()) {
 
  212             throw QueryError(
"Ignoring unknown higher-order command '{}'", functor);
 
  215             return needle->second.runCommand(((
Conjunction *) argsFormula.get())->formulae());
 
  217             return needle->second.runCommand({argsFormula});
 
  222         auto ctx = std::make_shared<QueryContext>(
 
  227             bool isQueryHandled = 
false;
 
  231             size_t pos = queryString.find_first_of(
'(');
 
  232             if (pos != std::string::npos) {
 
  233                 auto functor = queryString.substr(0, pos);
 
  234                 auto needle = higherOrderCommands_.find(functor);
 
  235                 if (needle != higherOrderCommands_.end()) {
 
  236                     runHigherOrderCommand(functor, queryString.substr(pos));
 
  237                     isQueryHandled = 
true;
 
  242             if (!isQueryHandled) {
 
  244                 auto query = std::make_shared<FormulaQuery>(phi, ctx);
 
  246                     auto p = std::dynamic_pointer_cast<Predicate>(query->formula());
 
  247                     auto needle = firstOrderCommands_.find(p->functor()->stringForm());
 
  248                     if (needle != firstOrderCommands_.end()) {
 
  249                         needle->second.runCommand(p->arguments());
 
  250                         isQueryHandled = 
true;
 
  253                 if (!isQueryHandled) {
 
  258         catch (std::exception &e) {
 
  259             std::cout << e.what() << std::endl;
 
  262         history_.append(queryString);
 
  263         history_.save(historyFile_);
 
  266     void runQuery(
const std::shared_ptr<const FormulaQuery> &query) {
 
  268         auto resultStream = kb_->submitQuery(query->formula(), query->ctx());
 
  269         auto resultQueue = resultStream->createQueue();
 
  273             auto nextResult = resultQueue->pop_front();
 
  275             if (nextResult->indicatesEndOfEvaluation()) {
 
  278                 auto answer = std::static_pointer_cast<const Answer>(nextResult);
 
  280                 if (answer->isPositive()) {
 
  281                     auto positiveAnswer = std::static_pointer_cast<const AnswerYes>(answer);
 
  282                     if (positiveAnswer->substitution()->empty()) {
 
  283                         std::cout << 
"yes." << std::endl;
 
  287                         pushQueryResult(positiveAnswer);
 
  291                     std::cout << *answer;
 
  297         if (numSolutions_ == 0) {
 
  298             std::cout << 
"no." << std::endl;
 
  303         std::cout << std::endl;
 
  304         runQuery(currentQuery_);
 
  305         if (!has_stop_request_) {
 
  306             std::cout << std::endl << PROMPT << std::flush;
 
  307             currentQuery_.clear();
 
  312     void insert(
char c) {
 
  313         if (cursor_ < currentQuery_.length()) {
 
  314             auto afterInsert = currentQuery_.substr(cursor_);
 
  315             std::cout << c << afterInsert <<
 
  316                       "\033[" << afterInsert.length() << 
"D" <<
 
  318             currentQuery_.insert(currentQuery_.begin() + cursor_, c);
 
  320             std::cout << c << std::flush;
 
  327         if (cursor_ < currentQuery_.length()) {
 
  328             auto afterInsert = currentQuery_.substr(cursor_);
 
  329             std::cout << str << afterInsert <<
 
  330                       "\033[" << afterInsert.length() << 
"D" <<
 
  332             currentQuery_.insert(currentQuery_.begin() + cursor_, str.begin(), str.end());
 
  334             std::cout << str << std::flush;
 
  335             currentQuery_ += str;
 
  337         cursor_ += str.length();
 
  341         auto oldLength = currentQuery_.length();
 
  342         auto newLength = queryString.length();
 
  344         std::cout << 
"\r" << PROMPT << queryString;
 
  346         for (
auto counter = oldLength; counter > newLength; --counter) {
 
  350         if (oldLength > newLength) {
 
  351             std::cout << 
"\033[" << (oldLength - newLength) << 
"D";
 
  353         std::cout << std::flush;
 
  354         currentQuery_ = queryString;
 
  355         cursor_ = currentQuery_.length();
 
  360         auto itr = currentQuery_.rbegin();
 
  361         while (itr != currentQuery_.rend() && isalpha(*itr)) { ++itr; }
 
  362         auto lastWord = 
std::string(itr.base(), currentQuery_.end());
 
  365         std::optional<std::string> namespaceAlias;
 
  366         if (itr != currentQuery_.rend() && *itr == 
':') {
 
  370             while (itr != currentQuery_.rend() && isalpha(*itr)) { ++itr; }
 
  371             namespaceAlias = 
std::string(itr.base(), aliasEnd.base());
 
  374         if (namespaceAlias.has_value()) {
 
  375             autoCompleteLocal(lastWord, namespaceAlias.value());
 
  377             autoCompleteGlobal(lastWord);
 
  381     bool autoCompleteCurrentWord(
const std::string &word, 
const std::string_view &completion) {
 
  382         auto mismatch = std::mismatch(word.begin(), word.end(), completion.begin());
 
  383         if (mismatch.first == word.end()) {
 
  385             auto remainder = 
std::string(mismatch.second, completion.end());
 
  393         std::vector<std::string_view> aliases;
 
  400         if (aliases.size() == 1) {
 
  402             if (autoCompleteCurrentWord(word, aliases[0])) {
 
  406         } 
else if (aliases.size() > 1) {
 
  407             displayOptions(aliases);
 
  416         if (uri.has_value()) {
 
  417             auto partialIRI = uri.value().get() + word;
 
  418             auto propertyOptions = kb_->vocabulary()->getDefinedPropertyNamesWithPrefix(partialIRI);
 
  419             auto classOptions = kb_->vocabulary()->getDefinedClassNamesWithPrefix(partialIRI);
 
  420             size_t namePosition = uri.value().get().length() + 1;
 
  424             std::vector<std::string_view> 
options(propertyOptions.size() + classOptions.size());
 
  426             for (
auto &
iri: propertyOptions) 
options[index++] = 
iri.substr(namePosition);
 
  427             for (
auto &
iri: classOptions) 
options[index++] = 
iri.substr(namePosition);
 
  431                 if (autoCompleteCurrentWord(word, 
options[0])) {
 
  434             } 
else if (
options.size() > 1) {
 
  439             KB_WARN(
"the namespace alias '{}' is unknown.", nsAlias);
 
  445         if (
options.empty()) 
return "";
 
  447         std::string_view commonPrefix = 
options[0];
 
  449             auto mismatchPair = std::mismatch(
 
  450                     commonPrefix.begin(),
 
  454             commonPrefix = std::string_view(
 
  455                     commonPrefix.begin(),
 
  456                     mismatchPair.first - commonPrefix.begin());
 
  458         return commonPrefix.data();
 
  461     void displayOptions(
const std::vector<std::string_view> &
options) {
 
  463         auto commonPrefix = getCommonPrefix(
options);
 
  464         if (!commonPrefix.empty()) {
 
  466             insert(commonPrefix);
 
  474         std::cout << optionsStr << PROMPT << currentQuery_ << std::flush;
 
  480         } 
else if (cursor_ < currentQuery_.length()) {
 
  481             auto afterDelete = currentQuery_.substr(cursor_);
 
  482             std::cout << 
'\b' << afterDelete << 
' ' <<
 
  483                       "\033[" << (afterDelete.length() + 1) << 
"D" << std::flush;
 
  484             currentQuery_.erase(cursor_ - 1, 1);
 
  486             std::cout << 
'\b' << 
' ' << 
'\b' << std::flush;
 
  487             currentQuery_.pop_back();
 
  494             std::cout << 
"\033[" << cursor_ << 
"D" << std::flush;
 
  500         if (cursor_ < currentQuery_.length()) {
 
  501             std::cout << 
"\033[" << (currentQuery_.length() - cursor_) << 
"C" << std::flush;
 
  502             cursor_ = currentQuery_.length();
 
  508             std::cout << 
"\033[1D" << std::flush;
 
  514         if (cursor_ < currentQuery_.length()) {
 
  515             std::cout << 
"\033[1C" << std::flush;
 
  522         if (history_.hasSelection()) {
 
  523             setQuery(history_.getSelection());
 
  528         history_.previousItem();
 
  529         setQuery(history_.hasSelection() ? history_.getSelection() : 
"");
 
  532     void handleEscapeSequence() {
 
  534         if (getch() == 
'[') {
 
  558     bool exitTerminal() {
 
  559         has_stop_request_ = 
true;
 
  564         std::cout << 
"Welcome to KnowRob." << 
'\n' <<
 
  565                   "For online help and background, visit http://knowrob.org/" << 
'\n' <<
 
  568         std::cout << PROMPT << std::flush;
 
  569         while (!has_stop_request_) {
 
  570             const auto c = getch();
 
  575                     handleEscapeSequence();
 
  597     std::atomic<bool> has_stop_request_;
 
  602     QueryHistory history_;
 
  603     std::map<std::string, TerminalCommand<TermPtr>, std::less<>> firstOrderCommands_;
 
  604     std::map<std::string, TerminalCommand<FormulaPtr>> higherOrderCommands_;
 
  608 int run(
int argc, 
char **argv) {
 
  609     po::options_description general(
"General options");
 
  610     general.add_options()
 
  611             (
"help", 
"produce a help message")
 
  612             (
"verbose", 
"print informational messages")
 
  613             (
"config-file", po::value<std::string>()->required(), 
"a configuration file in JSON format")
 
  614             (
"version", 
"output the version number");
 
  617     po::options_description visible(
"Allowed options");
 
  618     visible.add(general);
 
  620     po::variables_map vm;
 
  621     po::store(po::parse_command_line(argc, argv, visible), vm);
 
  623     if (vm.count(
"help")) {
 
  624         std::cout << visible;
 
  629     boost::property_tree::ptree config;
 
  630     if (vm.count(
"config-file")) {
 
  631         boost::property_tree::read_json(
 
  632                 vm[
"config-file"].as<std::string>(),
 
  635         std::cout << 
"'config-file' commandline argument is missing" << std::endl;
 
  640     auto log_config = config.get_child_optional(
"logging");
 
  646                          vm.count(
"verbose") ? spdlog::level::debug : spdlog::level::warn);
 
  648     return KnowRobTerminal(config).run();
 
  652 int main(
int argc, 
char **argv) {
 
  656         status = 
run(argc, argv);
 
  658     catch (std::exception &e) {
 
  659         KB_ERROR(
"a '{}' exception occurred in main loop: {}.", 
typeid(e).name(), e.what());
 
  660         status = EXIT_FAILURE;
 
static bool assertStatements(const KnowledgeBasePtr &kb, const std::vector< FormulaPtr > &args)
static void setSinkLevel(SinkType sinkType, spdlog::level::level_enum log_level)
static void loadConfiguration(boost::property_tree::ptree &config)
static std::vector< std::string_view > getAliasesWithPrefix(std::string_view prefix)
static OptionalStringRef aliasToUri(std::string_view alias)
static FormulaPtr parse(const std::string &queryString)
FunctionRule & function()
std::shared_ptr< KnowledgeBase > KnowledgeBasePtr
@ QUERY_FLAG_ALL_SOLUTIONS
void InitKnowRob(int argc, char **argv, bool initPython=true)
std::shared_ptr< const Answer > AnswerPtr
IRIAtomPtr iri(std::string_view ns, std::string_view name)
int main(int argc, char **argv)
int run(int argc, char **argv)