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)