- Documentation
- Reference manual
- Packages
- A C++ interface to SWI-Prolog
- A C++ interface to SWI-Prolog
- Summary of changes between Versions 1 and 2
- A simple example
- Sample code
- Introduction
- The life of a PREDICATE
- Overview
- Examples
- Rationale for changes from version 1
- Porting from version 1 to version 2
- The class PlFail
- Overview of accessing and changing values
- The class PlRegister
- The class PlQuery
- The PREDICATE and PREDICATE_NONDET macros
- Exceptions
- Embedded applications
- Considerations
- Conclusions
- A C++ interface to SWI-Prolog
- A C++ interface to SWI-Prolog
1.13 The class PlQuery
This class encapsulates the call-backs onto Prolog.
- PlQuery :: PlQuery(const char *name, const PlTermv &av, int flags = PL_Q_PASS_EXCEPTION)
- Create a query where name defines the name of the predicate
and
av the argument vector. The arity is deduced from av.
The predicate is located in the Prolog module
user. - PlQuery :: PlQuery(const char *module, const char *name, const PlTermv &av, int flags = PL_Q_PASS_EXCEPTION)
- Same, but performs the predicate lookup in the indicated module.
- int PlQuery::next_solution()
- Provide the next solution to the query. Yields
trueif successful andfalseif there are no (more) solutions. Prolog exceptions are mapped to C++ exceptions. If the PlQuery object was created with thePL_Q_EXT_STATUSflag, the extended return codes can also be returned (TRUE,FALSE,PL_S_NOT_INNER,PL_S_EXCEPTION,PL_S_FALSE,PL_S_TRUE,PL_S_LAST). Because of this, you shouldn't use PlCheckFail() with PlQuery::next_solution() in this situation. - void PlQuery::cut()
- Discards the query, but does not delete an of the data created by the
query. If there is any pending Prolog exception, it is mapped to a C++
exception and thrown. The call to PlQuery::cut()
is done implicitly by
PlQuery’s destructor.Below is an example listing the currently defined Prolog modules to the terminal.
PREDICATE(list_modules, 0) { PlTermv av(1); PlQuery q("current_module", av); while( q.next_solution() ) cout << av[0].as_string() << endl; return true; }
In addition to the above, the following functions have been defined.
- int PlCall(const char *predicate, const PlTermv &av)
- Creates a
PlQueryfrom the arguments generates the first next_solution() and destroys the query. Returns the result of next_solution() or an exception. - int PlCall(const char *module, const char *predicate, const PlTermv &av)
- Same, locating the predicate in the named module.
- int PlCall(const wchar_t *goal)
- int PlCall(const std::string& goal)
- Translates goal into a term and calls this term as the other PlCall() variations. Especially suitable for simple goals such as making Prolog load a file.
- bool PlTerm::call()
- Wrapper for PL_call(), returning
trueorfalsefor the success/failure of the call; and throws an exception if there's an error.t.call()is essentially the same asPlCall(t). - bool PlTerm::call(PlModule m)
- Same as PlTerm::call() but specifying the module.
1.13.1 The class PlFrame - Unification and foreign frames
As documented with PL_unify(), if a unification call fails and
control isn't made immediately to Prolog, any changes made by
unification must be undone. The functions PL_open_foreign_frame(),
PL_rewind_foreign_frame(), PL_discard_foreign_frame(), and
PL_close_foreign_frame() are encapsulated in the class
PlFrame, whose destructor calls
PL_close_foreign_frame(). Using this, the example code with PL_unify()
can be written:
PREDICATE(can_unify_ffi, 2)
{ fid_t fid = PL_open_foreign_frame();
int rval = PL_unify(A1.unwrap(), A2.unwrap());
PL_discard_foreign_frame(fid);
return rval;
}
/* equivalent to the Prolog code
T1 = T2 -> do_one_thing ; do_another_thing */
{ PlFrame fr;
bool t1_t2_unified = A1.unify_term(A2);
if ( ! t1_t2_unified )
fr.rewind();
if ( t1_t2_unified )
do_one_thing(...);
else
do_another_thing(...);
}
The following is C++ version of the code example for PL_open_foreign_frame(). The calls to PL_close_foreign_frame() and the check for PL_exception(0) in the C code aren't needed in the C++ code:
static std::vector<std::string> lookup_unifies =
{ "item(one, 1)", "item(two, 2)", "item(three, 3)" };
PREDICATE(lookup_unify, 1)
{ PlFrame fr;
for (auto& s : lookup_unifies )
{ PlCompound t(s);
if ( A1.unify_term(t) )
return true;
fr.rewind();
}
return false;
}
or using this convenience wrapper:
if ( RewindOnFail([t1=A1,t2=A2]()->bool
{ return t1.unify_term(t2); }) )
do_one_thing(...);
else
do_another_thing(...);
Note that PlTerm::unify_term()
checks for an error and throws an exception to Prolog; if you wish to
handle exceptions, you must call PL_unify_term(t1. unwrap(),t2.
unwrap()).
The class PlFrame provides an interface to discard
unused term-references as well as rewinding unifications (data-backtracking).
Reclaiming unused term-references is automatically performed after a
call to a C++-defined predicate has finished and returns control to
Prolog. In this scenario PlFrame is rarely of any use. This
class comes into play if the toplevel program is defined in C++ and
calls Prolog multiple times. Setting up arguments to a query requires
term-references and using PlFrame is the only way to
reclaim them.
Another use of of PlFrame is when multiple separate
unifications are done - if any of them fails, then the earlier
unifications must be undone before returning to Prolog.
- PlFrame :: PlFrame()
- Creating an instance of this class marks all term-references created afterwards to be valid only in the scope of this instance.
- ~ PlFrame()
- Reclaims all term-references created after constructing the instance. If either close() or discard() have been called, the destructor does nothing.
- void PlFrame::rewind()
- Discards all term-references and global-stack data created as well as undoing all unifications after the instance was created.
- void PlFrame::close()
- Reclaims all term-references created after constructing the instance.
- void PlFrame::discard()
- Same as PlFrame::rewind() + PlFrame::close().
- bool PlRewindOnFail(std::function<bool()> f)
- is a convenience function that does a frame rewind if a function call
fails (typically, failure due to unification failure). It takes a
std::function<bool>()> as an argument, which is
called in the context of a new
PlFrame.
A typical use for PlFrame is
the definition of C++ functions that call Prolog and may be called
repeatedly from C++. Consider the definition of assertWord(),
adding a fact to word/1;
the PlFrame removes the new term av[0] from
the stack, which prevents the stack from growing each time assertWord()
is called:
void
assertWord(const char *word)
{ PlFrame fr;
PlTermv av(1);
av[0] = PlCompound("word", PlTermv(word));
PlQuery q("assert", av);
PlCheckFail(q.next_solution());
}
The following example uses PlFrame in the context of a
foreign predicate. The can_unify/2’s
truth-value is the same as for Prolog unification (=/2), but has no side
effects. In Prolog one would use double negation to achieve this:
PREDICATE(can_unify, 2)
{ PlFrame fr;
int rval = (A1=A2);
fr.discard(); // or, less efficiently: fr.rewindd();
return rval;
}
Here is an example of using PlRewindOnFail(),
where name_to_terms contains a map from names to terms
(which are made global by using the PL_record() function). The
frame rewind is needed in the situation where the first unify_term()
succeeds and the second one fails.
static const std::map<const std::string, PlRecord> name_to_term =
{ {"a", PlTerm(...).record(), PlTerm(...).record()},
... };
PREDICATE(name_to_terms, 3)
{ A1.must_be_atom_or_string();
const auto it = name_to_term.find(A1.as_string());
return it != name_to_term.cend() &&
PlRewindOnFail([t1=A2,t2=A3,&it]()
{ return t1.unify_term(it->second.first.term()) &&
t2.unify_term(it->second.second.term()); });
}
The equivalent code without using PlRewindOnFail() is:
PREDICATE(name_to_terms, 3)
{ PlTerm key(A1), term1(A2), term2(A3);
const auto it = name_to_term.find(key.as_string());
if ( it == name_to_term.cend() )
return false;
if ( !term1.unify_term(it->second.first.term()) )
return false;
PlFrame fr;
if ( !term2.unify_term(it->second.second.term()) )
{ fr.discard();
return false;
}
return true;
}