knowrob  2.1.0
A Knowledge Base System for Cognition-enabled Robots
PrologEngine.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of KnowRob, please consult
3  * https://github.com/knowrob/knowrob for license details.
4  */
5 
6 #include <SWI-Prolog.h>
7 #include <filesystem>
8 #include <clocale>
9 #include <cstdlib>
10 
11 #include "knowrob/Logger.h"
12 #include "knowrob/integration/prolog/PrologEngine.h"
13 #include "knowrob/knowrob.h"
14 #include "knowrob/queries/QueryError.h"
15 #include "knowrob/integration/prolog/logging.h"
16 #include "knowrob/integration/prolog/ext/algebra.h"
17 #include "knowrob/semweb/PrefixRegistry.h"
18 #include "knowrob/URI.h"
19 #include "knowrob/reasoner/prolog/semweb.h"
20 
21 using namespace knowrob;
22 
23 static const int prologQueryFlags = PL_Q_CATCH_EXCEPTION | PL_Q_NODEBUG;
24 
26 
27 std::vector<std::string> PrologEngine::arguments_ = std::vector<std::string>();
28 
29 std::optional<PrologEngine> PrologEngine::self_ = std::nullopt;
30 
31 
32 PrologEngine::PrologEngine(uint32_t maxNumThreads)
33  : ThreadPool(maxNumThreads) {
34  finalizeWorker_ = [] {
35  // destroy the engine previously bound to this thread
36  PL_thread_destroy_engine();
37  KB_DEBUG("destroyed Prolog engine");
38  };
40 }
41 
43  // call PL_thread_attach_engine once initially for each worker thread
44  if (PL_thread_attach_engine(nullptr) < 0) {
45  // `-1` indicates an error, and `-2` that Prolog is compiled without multithreading support
46  KB_ERROR("Failed to attach Prolog engine to current thread!");
47  return false;
48  } else {
49  // if PL_thread_attach_engine()>0, then the Prolog ID for the thread was returned
50  KB_DEBUG("Attached Prolog engine to current thread.");
51  return true;
52  }
53 }
54 
55 foreign_t pl_rdf_register_namespace2(term_t prefix_term, term_t uri_term) {
56  char *prefix, *uri;
57  if (PL_get_atom_chars(prefix_term, &prefix) && PL_get_atom_chars(uri_term, &uri)) {
59  }
60  return TRUE;
61 }
62 
63 foreign_t url_resolve2(term_t t_url, term_t t_resolved) {
64  char *url;
65  if (PL_get_atom_chars(t_url, &url)) {
66  auto resolved = URI::resolve(url);
67  return PL_unify_atom_chars(t_resolved, resolved.c_str());
68  }
69  return false;
70 }
71 
73  if (isPrologInitialized_) return;
74  // toggle on flag
75  isPrologInitialized_ = true;
76 
77  // PL_initialise changes the locale. This can have bad consequences.
78  // I experienced unit tests for Redland failing because of this!
79  // Seems like it is related to parsing of numbers. Setting
80  // LC_NUMERIC to "C" before PL_initialise seems to fix the problem.
81  setenv("LC_NUMERIC", "C", 1);
82 
83  int pl_ac = 0;
84  char *pl_av[5];
85  arguments_.resize(4);
86  arguments_[pl_ac++] = getNameOfExecutable();
87  // '-g true' is used to suppress the welcome message
88  arguments_[pl_ac++] = "-g";
89  arguments_[pl_ac++] = "true";
90  // Inhibit any signal handling by Prolog
91  arguments_[pl_ac++] = "--signals=false";
92  for (int i = 0; i < pl_ac; i++) {
93  pl_av[i] = (char *) arguments_[i].c_str();
94  }
95  PL_initialise(pl_ac, pl_av);
96  KB_DEBUG("Prolog has been initialized.");
97  self_.emplace(std::thread::hardware_concurrency());
98 
99  // expand the Prolog library_directory path used to locate Prolog files
101 
102  // register some foreign predicates, i.e. cpp functions that are used to evaluate predicates.
103  // note: the predicates are loaded into module "user"
104  PL_register_foreign("knowrob_register_namespace",
105  2, (pl_function_t) pl_rdf_register_namespace2, 0);
106  PL_register_foreign("url_resolve",
107  2, (pl_function_t) url_resolve2, 0);
108  PL_register_foreign("log_message", 2, (pl_function_t) prolog::log_message2, 0);
109  PL_register_foreign("log_message", 4, (pl_function_t) prolog::log_message4, 0);
110  PL_register_extensions_in_module("algebra", prolog::PL_extension_algebra);
111  PL_register_extensions_in_module("semweb", prolog::PL_extension_semweb);
112  KB_DEBUG("common foreign Prolog modules have been registered.");
113 
114  consult(std::filesystem::path("integration") / "prolog" / "__init__.pl", "user");
115  KB_DEBUG("KnowRob __init__.pl has been consulted.");
116 }
117 
119  if (!isPrologInitialized_) return;
120  // finalize the Prolog engine
121  self_->shutdown();
122  PL_cleanup(0);
123  arguments_.clear();
124  self_ = std::nullopt;
125  // toggle off flag
126  isPrologInitialized_ = false;
127  KB_DEBUG("Prolog has been finalized.");
128 }
129 
131  static std::filesystem::path projectPath(KNOWROB_SOURCE_DIR);
132  static std::filesystem::path installPath(KNOWROB_INSTALL_PREFIX);
133 
134  // expand library search path, e.g. used by use_module/2 to locate Prolog source files.
135  // prefer files from projectPath
136  auto libPaths = {
137  installPath / "share" / "knowrob" / "integration" / "prolog",
138  installPath / "share" / "knowrob" / "reasoner" / "prolog",
139  installPath / "share" / "knowrob" / "reasoner",
140  installPath / "share" / "knowrob",
141  projectPath / "src" / "integration" / "prolog",
142  projectPath / "src" / "reasoner" / "prolog",
143  projectPath / "src" / "reasoner",
144  projectPath / "src"
145  };
146  for (const auto &p: libPaths) {
147  if (!exists(p)) continue;
148  eval([&p] {
149  return PrologTerm("asserta", PrologTerm(":", "user", PrologTerm("library_directory", p.string())));
150  });
151  }
152 
153  // expand file search path, e.g. used by absolute_file_name/3 predicate
154  // "knowrob" files:
155  auto filePaths_knowrob = {
156  projectPath,
157  installPath / "share" / "knowrob"
158  };
159  for (const auto &p: filePaths_knowrob) {
160  if (!exists(p)) continue;
161  eval([&p] {
162  return PrologTerm("assertz", PrologTerm("file_search_path", "knowrob", p.string()));
163  });
164  }
165 
166  // "test" files:
167  auto filePaths_tests = {
168  projectPath / "tests"
169  };
170  for (const auto &p: filePaths_tests) {
171  if (!exists(p)) continue;
172  eval([&p] {
173  return PrologTerm("assertz", PrologTerm("file_search_path", "test", p.string()));
174  });
175  }
176 }
177 
178 void PrologEngine::pushGoal(const std::shared_ptr<ThreadPool::Runner> &goal, const ErrorHandler &errHandler) {
179  self_->pushWork(goal, errHandler);
180 }
181 
182 void PrologEngine::pushGoalAndJoin(const std::shared_ptr<ThreadPool::Runner> &goal) {
183  // push goal and wait for termination
184  std::exception_ptr excPtr = nullptr;
185  pushGoal(goal, [&excPtr](...) { excPtr = std::current_exception(); });
186  goal->join();
187  // rethrow any exceptions in this thread
188  if (excPtr) std::rethrow_exception(excPtr);
189 }
190 
191 bool PrologEngine::eval(const GoalFactory &goalFactory) {
192  bool hasSolution = false;
193 
194  pushGoalAndJoin(std::make_shared<LambdaRunner>(
195  [&goalFactory, &hasSolution](const LambdaRunner::StopChecker &) {
196  auto goal = goalFactory();
197  auto qid = goal.openQuery(prologQueryFlags);
198  hasSolution = goal.nextSolution(qid);
199  PL_close_query(qid);
200  }));
201 
202  return hasSolution;
203 }
204 
205 std::optional<PrologEngine::Solution> PrologEngine::oneSolution(const GoalFactory &goalFactory) {
206  std::optional<Solution> solution;
207 
208  pushGoalAndJoin(std::make_shared<LambdaRunner>(
209  [&goalFactory, &solution](const LambdaRunner::StopChecker &) {
210  auto goal = goalFactory();
211  auto qid = goal.openQuery(prologQueryFlags);
212  if (goal.nextSolution(qid)) {
213  solution = Solution();
214  for (auto &var: goal.vars()) {
215  solution.value()[var.first] = PrologTerm::toKnowRobTerm(var.second);
216  }
217  }
218  PL_close_query(qid);
219  }));
220 
221  return solution;
222 }
223 
224 std::vector<PrologEngine::Solution> PrologEngine::allSolutions(const GoalFactory &goalFactory) {
225  std::vector<Solution> solutions;
226 
227  pushGoalAndJoin(std::make_shared<LambdaRunner>(
228  [&goalFactory, &solutions](const LambdaRunner::StopChecker &hasStopRequest) {
229  auto goal = goalFactory();
230  auto qid = goal.openQuery(prologQueryFlags);
231  while (!hasStopRequest() && goal.nextSolution(qid)) {
232  Solution solution;
233  for (auto &var: goal.vars()) {
234  solution[var.first] = PrologTerm::toKnowRobTerm(var.second);
235  }
236  solutions.push_back(solution);
237  }
238  PL_close_query(qid);
239  }));
240 
241  return solutions;
242 }
243 
244 void PrologEngine::query(const GoalFactory &goalFactory, const BindingsHandler &callback) {
245  pushGoalAndJoin(std::make_shared<LambdaRunner>(
246  [&goalFactory, &callback](const LambdaRunner::StopChecker &hasStopRequest) {
247  auto goal = goalFactory();
248  auto qid = goal.openQuery(prologQueryFlags);
249  while (!hasStopRequest() && goal.nextSolution(qid)) {
250  auto bindings = std::make_shared<Bindings>();
251  for (auto &var: goal.vars()) {
252  if (PL_term_type(var.second) == PL_VARIABLE) {
253  continue;
254  }
255  bindings->set(std::make_shared<Variable>(var.first),
257  }
258  callback(bindings);
259  }
260  PL_close_query(qid);
261  }));
262 }
263 
264 bool PrologEngine::consult(const std::filesystem::path &uri, const char *module) {
265  static auto consult_f = "consult";
266  auto path = PrologEngine::getPrologPath(uri);
267 
268  return PrologEngine::eval([&]() {
269  PrologTerm plTerm(consult_f, path.native());
270  if (module) plTerm.setModule(module);
271  return plTerm;
272  });
273 }
274 
275 std::filesystem::path PrologEngine::getPrologPath(const std::filesystem::path &filePath) {
276  static std::filesystem::path projectPath(KNOWROB_SOURCE_DIR);
277  static std::filesystem::path installPath(KNOWROB_INSTALL_PREFIX);
278 
279  if (!exists(filePath)) {
280  auto possiblePaths = {
281  projectPath / filePath,
282  projectPath / "src" / filePath,
283  projectPath / "src" / "reasoner" / "prolog" / filePath,
284  installPath / "share" / "knowrob" / filePath
285  };
286  for (const auto &p: possiblePaths) {
287  if (exists(p)) return p;
288  }
289  }
290  return filePath;
291 }
292 
293 std::filesystem::path PrologEngine::getResourcePath(const std::filesystem::path &filePath) {
294  static std::filesystem::path projectPath(KNOWROB_SOURCE_DIR);
295  static std::filesystem::path installPath(KNOWROB_INSTALL_PREFIX);
296 
297  if (!exists(filePath)) {
298  auto possiblePaths = {
299  projectPath / filePath,
300  installPath / "share" / "knowrob" / filePath
301  };
302  for (const auto &p: possiblePaths) {
303  if (exists(p)) return p;
304  }
305  }
306  return filePath;
307 }
foreign_t pl_rdf_register_namespace2(term_t prefix_term, term_t uri_term)
foreign_t url_resolve2(term_t t_url, term_t t_resolved)
#define KB_DEBUG
Definition: Logger.h:25
#define KB_ERROR
Definition: Logger.h:28
static void registerPrefix(std::string_view prefix, std::string_view uri)
static std::vector< std::string > arguments_
Definition: PrologEngine.h:118
std::map< Variable, TermPtr, std::less<> > Solution
Definition: PrologEngine.h:26
static void initializeProlog()
static std::optional< Solution > oneSolution(const GoalFactory &goalFactory)
static void finalizeProlog()
static bool consult(const std::filesystem::path &uri, const char *module={})
PrologEngine(uint32_t maxNumThreads=0)
static std::filesystem::path getPrologPath(const std::filesystem::path &filename)
static std::optional< PrologEngine > self_
Definition: PrologEngine.h:119
static bool isPrologInitialized_
Definition: PrologEngine.h:117
static void pushGoal(const std::shared_ptr< ThreadPool::Runner > &goal, const ErrorHandler &errHandler)
std::function< PrologTerm()> GoalFactory
Definition: PrologEngine.h:25
ThreadPool::ExceptionHandler ErrorHandler
Definition: PrologEngine.h:24
static void pushGoalAndJoin(const std::shared_ptr< ThreadPool::Runner > &goal)
static bool eval(const GoalFactory &goalFactory)
static std::filesystem::path getResourcePath(const std::filesystem::path &filename)
static void expandSearchPaths()
static void query(const GoalFactory &goalFactory, const BindingsHandler &callback)
static std::vector< Solution > allSolutions(const GoalFactory &goalFactory)
bool initializeWorker() override
void setModule(std::string_view module)
Definition: PrologTerm.h:166
TermPtr toKnowRobTerm() const
Definition: PrologTerm.cpp:691
std::function< bool()> StopChecker
Definition: ThreadPool.h:152
std::function< void()> finalizeWorker_
Definition: ThreadPool.h:168
static std::string resolve(const std::string_view &uriString)
Definition: URI.cpp:79
constexpr std::string_view prefix
Definition: owl.h:14
VariableRule & var()
Definition: terms.cpp:91
foreign_t log_message4(term_t level_term, term_t msg_term, term_t file_term, term_t line_term)
Definition: logging.cpp:61
PL_extension PL_extension_algebra[]
Definition: algebra.h:12
PL_extension PL_extension_semweb[]
Definition: semweb.h:12
foreign_t log_message2(term_t level_term, term_t msg_term)
Definition: logging.cpp:57
std::function< void(const BindingsPtr &)> BindingsHandler
Definition: Bindings.h:152
char * getNameOfExecutable()
Definition: knowrob.cpp:30