Manual for SwiProlog-C++ 0.1.0

Volker Wysk

2002-01-14


Table of Contents

1 Introduction
Status
Requirements
2 User Guide
The Global Term Components Graph
Initialisation
Working With Terms
Executing Queries
Documentation of the C Language Query Interface
Frames and Frame Membership
Foreign Predicates
Concepts and Models
Assignable
Default Constructible
Equality Comparable
Less Than Comparable
3 Reference
Starting and Restarting the Prolog Engine
Global Functions and Operators
unify
Comparision Operators
atom_to_term
Types
Atom
Frame
Functor
Module
Predicate
Query
RecordedTerm
StaticAtom
StaticFunctor
StaticPredicate
StaticQuery
StaticTerm
Term
TermType
ForeignPredicate
Exceptions
4 Implementation Notes
SWI-Prolog Memory Management
Semantics of Term Objects
Init Queues
Foreign Predicates
Frames
Queries

Chapter 1. Introduction

Table of Contents

Status
Requirements

SwiProlog-C++ is a wrapper around the SWI-Prolog Foreign Language Interface. It allows you to write parts of a program in Prolog, and other parts in C++. You can either embed the SWI-Prolog engine in a C++ application, using it as a logic component, or use C++ to define foreign predicates (or both).

SwiProlog-C++ aims at a smooth integration of the SWI-Prolog constructs in the C++ environment. It makes use of advanced C++ features, such as templates, namespaces and STL integration. It tries to shield the user from hard to understand memory management and other issues of the SWI-Prolog implementation. Using SwiProlog-C++ should be much easier than using the Foreign Language Interface.

Status

SwiProlog-C++ isn't finished yet. It is in a proceded state, and as far as it goes, is tested and mostly documented. It also should be quite mature and usable, since a lot of thought and reconsideration has gone into it. However, some things remain to be done. And it hasn't been in much usage yet.

  • Null bytes in atoms and Prolog strings aren't considered.

  • The main() function must be in the C++ part of the program.

  • Converting terms to strings should be done in such a way, that the strings are valid Prolog expressions (like write(..., [quoted(true)])).

  • Exceptions should conform to the ISO standard.

  • The documentation of how to write foreign predicates has not yet been written. See the test program for some examples.

Requirements

SWI-Prolog

I'm using version 4.0.9. Later versions should also work.

C++ compiler

It is tested with GCC 2.95, but any compliant C++ compiler should do. GCC 3.02 does not work, because of bugs.

Docbook-XML installation

The documentation is written using Docbook 4.1.2 (the XML version) and Normal Walsh's Docbook XML stylesheets 1.40 for formatting. The XSLT processor is xsltproc 1.0.0, but any should do. You need this only if you want to change the manual.

Chapter 2. User Guide

This section describes how to use SwiProlog-C++ and provides examples. The full details are covered in Chapter 3. Reference.

The Global Term Components Graph

Terms are the central data type. They reside in memory managed by the Prolog engine, and are accessed via handles from the C++ side. The handles are encapsulated by the class Term, which is the most important type for interfacing to Prolog.

All the terms form a global graph of term components. Terms can be atomic (atoms, numbers...), or compound. Compound terms consist of a functor ID and pointers to subterms. They can share common subterms. Interfacing to the Prolog engine is all about dealing with the terms in this global graph.

On the Prolog side, we have value semantics. This means, variables denote values, not storage places like in C++. Values can't be changed. Free variables denote values that are “not yet known”. Terms can contain free variables, which makes them “incomplete values”, a notion alien to imperative programming. (Things become more complicated if you use non-logical prediates.)

Seen from the C++ side, terms do change, by unification. Free variables can be bound. This replaces them by the terms they get bound to. They also change by backtracking, when bindings are undone. Subterms can be shared by several superterms. When a subterm changes, all the superterms change as well. You can't tell from a term which other terms share parts of it. Unification and de-unification (binding and unbinding) are the only way terms can change.

A Term object is a handle for a term in the global term component graph. It behaves (mostly) like a pointer. Such pointers are the only way to access terms. In particular, Term objects don't behave like term values. They are pointers.

Initialisation

In version 0.1.0 it isn't yet possible to have the main() function on the Prolog side. It must be a C++ function.

At the beginning, the Prolog engine must be initialised. This is done by prolog_init(). It also must be shut down at the end. SwiProlog-C++ makes sure this happens. It is also possible to explicitly shut down the Prolog engine and restart it.

A typical main() function looks like this.

int main(int argc, char** argv)
{
  try {
    prolog_init(argc, argv);

    // ...

  } catch (PlError& err) {
    cerr << "error: " << err.message() << endl;
    return 1;
  }
}

Anything involving the Prolog engine must be done after it is started. [In fact, not exactly anything, but let's pretend so]. This means that any Prolog related data (terms, atoms, functors, ...) can't be static. The following will not work. Instead, one would have to do the initialisation of any global or static data after the engine is ready.

Term t("foo(1,2,3)");

int main(int argc, char** argv)
{
  // ...
}

There is a mechanism in SwiProlog-C++ for handling such defered initialisation; and reinitialisation in the case of a restarted engine. All the affected classes have corresponding Static... variants. If they are initialised while the engine is not running, they defer the actual (Prolog-side) initialisation until the engine is ready. If it is restarted, they reinitialise. Apart from that, they behave mostly like their non-static counterparts. The above can be achieved like this.

StaticTerm t("foo(1,2,3)");

int main(int argc, char** argv)
{
  // ...
}

Working With Terms

A Term object is essentially a pointer into the global term component graph. However, there is a difference: they can't be compared. (There is no way to define comparision operators on the terms-as-pointers). This means, for instance, that terms can't be used as keys for sorted containers. The comparision operators which are defined for Term compare the terms pointed to, not the pointers.

Terms can be created from many data types, and they can be converted to those data types. This is very convenient, since terms can be used much like if they were the values they contain. Similarly, global comparision operators are defined. For instance:

Term myfunction(int i, Term t)
{
  if (i > t) {
    int ints[] = { 1, 2, 3 };
    return list<int>(ints, ints+3);
  }
  // ...
}

Term objects refer data managed by the Prolog engine. When new Term obejcts are created, new storage space is allocated inside the engine. It will eventually be garbage collected, if it is no longer referenced. But simply destroying the Term objects will not remove the references. (Because of the way the SWI-Prolog engine works, this isn't possible). In order to ensure that unused data will be deleted, frames must be used. See the section called “Frames and Frame Membership”.

C++ code called from the Prolog side (a foreign predicate) runs in an implicit frame, so ressources it allocated will eventually be reclaimed. But if the Prolog engine is embedded in C++ code (terms being manipulated outside any foreign predicate invocations) frames must be used explicitly. They should also be used in long computations inside foreign predicates.

Executing Queries

There are a number of ways, how queries can be used. ... XXX

If you know beforehand that the query has one solution, you can use a temporary Query object. The temporary object will be destroyed after the call of Query::next(), which closes the query. Like this:

Term t;
Query("my_pred", t).next();
// Now t holds the result of "my_pred(X)".

If you use implicit conversions to Term when supplying the arguments, you can still access the results via Query::operator [], like this:

void f(const char* arg)
{
  Query q("my_pred", arg);
  if (q.next()) {
    cout << q[0];
  }
}

Documentation of the C Language Query Interface

XXX

The documentation of the Foreign Language Interface functions which execute queries is very scanty in the SWI-Prolog Reference Manual (could be considered incomplete). So here follows a more explicit description of what happens.

PL_open_query

qid_t PL_open_query(module_t ctx, int flags, predicate_t p, term_t +t0);

This opens a query and sets a backtracking point, to which the following functions backtrack. It's like opening an implicit frame.

PL_next_solution

int PL_next_solution(qid_t qid);

The SWI-Prolog Reference Manual says: “Generate the first (next) solution for the given query. The return value is TRUE if a solution was found, or FALSE to indicate the query could not be proven. This function may be called repeatedly until it fails to generate all solutions to the query.”.

This does a full backtracking, to the point of the last call of PL_next_solution, or, in the case of the first call, to the call of PL_open_query which started the query. Even term references which have been created after the call of PL_open_query and before the first call of PL_next_solution are affected.

Bindings in the arguments of the query are undone. So are any bindings in any other terms. New term references are invaliated, and the data which they refer is no longer refered by them, so it can be garbage collected.

This is like rewinding the implicit frame which has been created by PL_open_query.

PL_cut_query

void PL_cut_query(qid_t qid);

In the SWI-Prolog Reference Manual: “Discards the query, but does not delete any of the data created by the query. It just invalidates qid, allowing for a new call to PL_open_query() in this context.”

This does not undo any bindings, which habe been made either in the arguments of the query or in any other terms. But it does invalidate any term references which have been created since the call of PL_open_query.

This is like closing the implicit frame which has been created by PL_open_query.

PL_close_query

void PL_close_query(qid_t qid);

In the SWI-Prolog Reference Manual: “As PL_cut_query(), but all data and bindings created by the query are destroyed.”

This does a full backtracking to the point where the query was opened. All bindings since are undong, and all term references created since are invalidated.

This is like discarding the implicit frame which has been created by PL_open_query.

PL_close_foreign_frame

void PL_close_foreign_frame(fid_t id);

SWI-Prolog Reference Manual: “Discard all term-references created after the frame was opened. All other Prolog data is retained. This function is called by the kernel whenever a foreign function returns control back to Prolog.”

Invalidate any new term references. Do not undo any bindings. Close the frame.

PL_discard_foreign_frame

void PL_discard_foreign_frame(fid_t id);

SWI-Prolog Reference Manual: “Same as PL_close_foreign_frame(), but also undo all bindings made since the open and destroy all Prolog data.”

Invalidate any new term references. Undo all new bindings. Close the frame.

PL_rewind_foreign_frame

void PL_rewind_foreign_frame(fid_t id);

SWI-Prolog Reference Manual: “Undo all bindings and discard all term-references created since the frame was created, but does not pop the frame. I.e. the same frame can be rewinded multiple times, and must eventually be closed or discarded.”

Invalidate any new term references. Undo all new bindings. Don't close the frame.

Frames and Frame Membership

Frames and frame membership do not need to be considered if terms are only manipulated inside foreign predicates, and no Term objects are kept between invocations of foreign predicates (such as storing them as global variables or on the heap). Otherwise, things become a bit more complicated.

Creating a new Term object consumes ressources in the Prolog engine in two ways. First, a cell on the so called “local stack”, is allocated. Stack cells are references to data (terms) held by the Prolog engine. Second, as long as such data is referenced, it will not be garbage collected. Assigning a new value to the Term object will not destroy the old reference.

In order to reclaim the stack cells, code portions are enclosed in frames. When the frame is left, all stack cells which have been allocated inside of it will be freed. Closing a frame has no other effect. After the local stack cells are no longer in use, the data they refered can be garbage collected. Frames nest. There is an implicit outermost frame, which encloses the entire execution of the Prolog engine (from initialisation until shutdown).

Every foreign predicate runs in an implicit frame. This is only the case if it is called by the Prolog engine, as a foreign predicate. Calling the same function from the C++ side, as an ordinary C++ function, does not make it run in a frame. Foreign predicates are discussed in the section called “Foreign Predicates”.

Every valid Term object is member of one of the currently active frames, because it uses a stack cell. Closing a frame invalidates any still existing Term objects which belong to that frame. Invalid Terms are allowed to exist, but they must not be used, except for assigning a new value.

When a new Term object is created, it becomes member of the currently active (innermost) frame. Frames are supposed to be used as automatic variables. When a Frame object is destroyed (when the variable goes out of scope), the corresponding frame is closed. Terms should be automatic variables as well, and be passed by value. When used this way, invalid Terms are unlikely to occur. For instance:

Term t1;
{
  Frame fr;
  Term t2 = "foo(X,Y)";
  func(t2);
  t1.unify(t2);
  // ...

} // t2 invalidated and out of scope, t1 unaffected

The following, on the other hand, will probably cause a crash.

Term* termptr;
{
  Frame fr;
  termptr = new Term;
  termptr->unify("foo(X,Y)");
}
cout << *termptr;

When a Term object is assigned a new value - being made point to another term - it changes frame membership. It “migrates” to the same frame as the other Term. (This unfortunate behaviour can't be avoided; see the implementation notes for details why).

Term t;
{
  Frame fr;
  t = 42;
} // t invalidated

cout << t; // crash

In the above example, t = 42 is the same as t = Term(42), and the new term “42” becomes member of the frame fr. t is then assigned this new term, and also becomes member of fr. It is invalidated when fr is left.

In order to avoid this, the following code should be used instead. t starts by pointing to a free variable, which can then be bound.

Term t;
{
  Frame fr;
  t.unify(42);
}

cout << t;

In the above example, the Term object isn't actually changed - the term pointed to is changed.

Yet to discuss is a more exotic way to assign to a Term, which should be avoided if at all possibe. It's the Term::assign() method. Sometimes it might be necessary to actually change the pointer, making it point to a different term. assign() does this. It does the same as an assignment, but without making the Term object migrate to a different frame. The example can also correctly be implemented like this. Again, a term is created from the integer 42 by an implicit conversion.

Term t;
{
  Frame fr;
  t.assign(42);
}

cout << t;

The drawback is that assign() can not be used if copies of the Term object exist. You must guarantee this, or you will break the class Term's semantics of behaving like pointers to terms. For instance:

Term t1 = 42;
Term t2 = t1;

// t1 == t2 == 42

t1.assign("foobar");

// Now *both*, t1 and t2 point to "foobar".

(The difference between Term::operator = () and Term::assign() is that the assignment operator makes the object use the same stack cell as the other term, whereas assign() changes the contents of the stack cell in use.)

When possible, unification should be used instead of assign(). Another option might be RecordedTerm.

So far, the use of frames for freeing unused memory space inside the Prolog engine has been discussed. There is another purpose for frames: causing backtracking.

Backtracking is caused by calling the frame's rewind() method. This will backtrack to the point where the frame was opened, undoing all unifications (bindings). The references to data created inside the Prolog engine are also destroyed. This means that all Term objects which belong to the frame are invalidated. Calling rewind() does not close the frame. It just goes back to the state when the frame has been opened.

Foreign Predicates

XXX not yet written

Concepts and Models

In order to precisely capture some of the semantics of the classes provided by SwiProlog-C++, this documentation uses the notions of concepts and models, as defined in SGI's documentation of the Standard Template Library.

In short, a concept specifies a set of syntactic expressions which must be valid for a type, and their meaning. A model of the concept is a type which satisfies all the requirements. The generic (template based) classes and functions of the STL can be used for types which are models of the specified concepts. See the introduction page of the SGI documentation for details.

The following concepts are used in SwiProlog-C++. The semantics are that some operations are defined the way you would expect.

Assignable

A type is Assignable if it is possible to copy objects of that type and to assign values of the type to variables. The copy constructor, assignment operator and swap method must be defined.

Entry in the SGI documentation

Default Constructible

A type is Default Constructible if it has a default constructor.

Entry in the SGI documentation

Equality Comparable

A type is Equality Comparable if objects of that type can be compared for equality using operator == (), and if it is an equivalence relation. operator == () and operator != () must be defined.

Entry in the SGI documentation

Less Than Comparable

A type is Less Than Comparable if it has the comparision operators >, <, >= and <= defined, and they implement a partial ordering.

Entry in the SGI documentation

Chapter 3. Reference

Starting and Restarting the Prolog Engine

void prolog_init(int argc, char const * const * argv);
void prolog_halt(void);

prolog_init initialises the Prolog engine. It must be called before anything which involves it can be performed. The parameters argc and argv are the ones from the main function. It calls the Foreign Language Interface function PL_initialise. This can fail for many reasons, in which case a GeneralError is thrown. See the SWI-Prolog Reference Manual for the exact actions which are taken.

prolog_halt shuts down the prolog engine. This is done automatically when the program exits, so you normally do not need to call this function.

You can shut down the prolog engine with prolog_halt and restart it with prolog_init. Note that this is very expensive. All data which lives inside the Prolog engine is destroyed in this process, all Term objects invalidated, etc.

Global Functions and Operators

unify

bool unify(const Term& t1, const Term& t2);

template <typename T>
bool unify(const Term& t, const T& v);

template <typename T>
bool unify(const T& v, const Term& t);

These functions are defined for convenience. They are the same as t1.unify(t2) and t.unify(v), respecitvely. See Term::unify().

Comparision Operators

template <typename V> bool operator == (const Term&, const V&);
template <typename V> bool operator == (const V&,    const Term&);
template <typename V> bool operator != (const Term&, const V&);
template <typename V> bool operator != (const V&,    const Term&);
template <typename V> bool operator >  (const Term&, const V&);
template <typename V> bool operator >  (const V&,    const Term&);
template <typename V> bool operator <  (const Term&, const V&);
template <typename V> bool operator <  (const V&,    const Term&);
template <typename V> bool operator >= (const Term&, const V&);
template <typename V> bool operator >= (const V&,    const Term&);
template <typename V> bool operator <= (const Term&, const V&);
template <typename V> bool operator <= (const V&,    const Term&);

These operators let you compare a term to a value of any type which it can be converted to. First they convert the Term parameter to the template type V. Then they apply the corresponding comparision operator. This way, a Term can be compared with a value of any type which it can be converted to, and which has the comparision operator defined. If the conversion fails, an exception is raised (at least for the conversions defined by SwiProlog-C++).

For instance, assume t is of type Term. Then t > 42 means ((int) t) > 42. This is different from t > (Term) 42, which compares t to 42 in standard term order. When both arguments are terms, they are always compared in standard term order. If you want to compare them as values of some type, you need to convert one of them. For instance, t1 > (int) t2.

atom_to_term

bool atom_to_term(const char* text, Term* term, list<Term::Binding>* bindings);

struct Term::Binding
{
  string name;
  Term   var;
  Binding(string n, Term v) : name(n), var(v) {}
};

Term::operator Binding () const;

This is an interface to the atom_to_term/3 predicate. The new term is stored in term, if successful, and the list of bindings is converted to list<Term::Binding> and stored in bindings. The return value if true if the conversion was successful.

The conversion operator converts a term in the form Name = Value to a Term::Binding. It is used by atom_to_term().

Types

Atom

The class Atom represents Prolog atoms. It is a model of Equality Comparable, Less Than Comparable and Assignable. Atom objects don't have any meaningful order, such as lexicographic. It is only guaranteed that the comparision operators implement a total ordering. This is enough to store atoms in sorted containers such as set<> or map<>. (In fact, the handles which the Prolog engine assigns are compared).

Atom::Atom(const char *);
Atom::Atom(string);

This creates an Atom which represents an already existing or newly created Prolog atom. The string is copied to memory managed by the Prolog engine.

Atom::Atom(const Term&);
Atom::Atom(string);

This extracts an atom from a term which only consists of that atom. If the term does not encapsulate one atom, then an ConversionError exception is raised.

bool Atom::operator == (const Atom&) const;
bool Atom::operator != (const Atom&) const;

bool Atom::operator >  (const Atom&) const;
bool Atom::operator <  (const Atom&) const;
bool Atom::operator >= (const Atom&) const;
bool Atom::operator <= (const Atom&) const;

Atom::Atom(const Atom&);
Atom::operator = (const Atom&);
void Atom::swap(Atom&);

These implement the concepts Equality Comparable, Less Than Comparable and Assignable.

Atom::operator const char * () const;

This returns the atom as a string. The string resides in static memory managed by the Prolog engine, and remains valid as long as it is not garbage collected. This means, as long as any term exists which uses the atom. It must not be changed.

The conversion requires one function call. This is relatively expensive.

ostream& operator << (ostream&, const Atom&);

This writes the atom as a string to the stream.

Frame

There are two purposes of frames: reclaiming ressources and retrying. Creating new Term objects will consume local stack cells in the Prolog engine. Enclosing a code portion in a frame will reclaim the stack cells when the frame is left, thereby invalidating any Term objects which might still be using them. Closing a frame has no other effect.

Rewinding a frame is a different thing. It has the effect of backtracking to the point where the frame was opened. All bindings (unifications) done since the frame was opened are undone, and all data created inside the Prolog engine is no longer referenced and therefore deleted. The local stack cells are also reclaimed.

Note that every foreign predicate invocation done by the Prolog engine runs inside an implicit frame.

Frame::Frame();

This opens a new frame. It will be closed when the object is destroyed. Normally, a Frame is an automatic variable, so the frame will be closed when the variable goes out of scope.

Frame::~Frame();

Destroying the Frame object closes the frame. All Term objects which belong to the frame are invalidated, and the associated ressources (cells on the local stack) reclaimed. Any unifications done with the terms pointed to stay in effect. This means that closing a frame does not cause backtracking. It is simimar to cutting a query.

void Frame::rewind();

Invalidate all Terms which belong to the frame, and undo all unifications done since the frame was created. This means: backtrack to the point where the frame has been created. This resembles closing a query. The frame stays open. This calls the Foreign Language Interface function PL_rewind_foreign_frame().

(There is no need for an equivalent of PL_discard_foreign_frame, because the effect is achieved by calling Frame::rewind() before the Frame object is destroyed).

Functor

A Functor object represents a name/arity pair, as used by Prolog for constructing terms.

Functor is a model of Equality Comparable, Less Than Comparable and Assignable. Two Functor objects are equal iff they represent the same functor. The comparision operations implement no particular ordering.

Functor::Functor(const char * name, int arity);
Functor::Functor(string name, int arity)

Make a new Functor object which represents a newly registered or an alreay existing one.

const char* Functor::name() const;
int Functor::arity() const;

Get name and arity of the functor, respecitvely. The pointer returned by name() points to storage managed by the Prolog engine.

string Functor::info() const;

Return name and arity of the functor as a string in the form ‘name/arity’.

bool Functor::operator == (const Functor&) const;
bool Functor::operator != (const Functor&) const;

bool Functor::operator >  (const Functor&) const;
bool Functor::operator <  (const Functor&) const;
bool Functor::operator >= (const Functor&) const;
bool Functor::operator <= (const Functor&) const;

Functor::Functor(const Functor&);
Functor::Functor operator = (const Functor&);
void Functor::swap(Functor&);

These implement the concepts Equality Comparable, Less Than Comparable and Assignable.

Module

A Module object holds a handle for a SWI-Prolog module. They are needed at various points inside SwiProlog-C++. Since const char * and string are convertible to Module, you can always specify the module's name instead.

Module is a model of Equality Comparable, Less Than Comparable and Assignable.

Module has a null value, created by the default constructor. Its meaning depends on the context.

explicit Module::Module(const char*);
explicit Module::Module(string);

These two constructors create a Module which represents either an already existing SWI-Prolog module, or a newly created one. The argument is the module name. Calling the first one with a null string has the same effect as the default constructor.

Module::Module();

This creates the Module null value.

const char* Module::name() const;

Return the name of the module.

static Module Module::context();

Return the current context module.

static Module Module::user();

Return the module with the name user. That's the main module in SWI-Prolog.

bool Module::operator == (const Module&) const;
bool Module::operator != (const Module&) const;

bool Module::operator >  (const Module&) const;
bool Module::operator <  (const Module&) const;
bool Module::operator >= (const Module&) const;
bool Module::operator <= (const Module&) const;

Module::Module(const Module&);
Module::Module operator = (const Module&);
void Module::swap(Module&);

These implement the concepts Equality Comparable, Less Than Comparable and Assignable.

Predicate

A Predicate differs from a Functor in that it also has an associated context module. It is used to refer to predicates, but the predicate does not acutally need to be defined. It rather represents a name-arity-module triple.

Predicate is a model of Equality Comparable, Less Than Comparable and Assignable. Two Predicate objects are equal iff they represent the same predicate. The comparision operations implement no particular ordering.

Predicate::Predicate(Functor, Module context_module = Module::context());

Create a new Predicate from functor and context module. The context module of the predicate defaults to the current context module.

Predicate::Predicate(const char* name, int arity, Module context_module = Module::context());

Create a new Predicate from name, arity and context module. The context module of the predicate defaults to the current context module.

void Predicate::info(char const ** name, int* arity, module_t * module) const;

Store name, arity and context module of the predicate in name, arity and module. The pointer stored in name points to memory managed by the Prolog engine.

const char* Predicate::name() const;
int Predicate::arity() const;
Module Predicate::module() const;

Get name, arity and context module of the predicate, respectively. The pointer returned by name() points to memory managed by the Prolog engine.

string Predicate::info() const;

Return a descriptive text for the predicate in the form ‘module:name/arity’.

bool Predicate::operator == (const Predicate&) const;
bool Predicate::operator != (const Predicate&) const;

bool Predicate::operator >  (const Predicate&) const;
bool Predicate::operator <  (const Predicate&) const;
bool Predicate::operator >= (const Predicate&) const;
bool Predicate::operator <= (const Predicate&) const;

Predicate::Predicate(const Predicate&);
Predicate Predicate::operator = (const Predicate&);
void swap(Predicate&);

These implement the concepts Equality Comparable, Less Than Comparable and Assignable.

Query

A Query is a simple and direct wrapper of the Foreign Language Interface query functions. The following limitations apply.

  • The query can by secified as the predicate to be called only. You can't directly specify a complex term as a query, like you can in Prolog. Any composite subterms of the predicate must be supplied as argument terms, which you must build and decompose on your own. StaticQuery provides a more comfortable interface.

  • When the frame in which the query was started is left, and the query is still running, it is implicitly closed.

  • The Prolog engine must be running before a Query object can be created. Normally, they are created as automatic variables.

  • Only one query at a time may be running, except for recursively invoked foreign predicates. This restriction is due to the Foreign Language Interface

  • The results are deunified when the next result is acquired. This is not really a limitation, but the way backtracking works.

Query::Query(const char * name, int arity, Module context_module = Module());
Query::Query(Predicate predicate, Module context_module = Module());

These two constructors create a new query from the predicate to be called and the context module. To the first one, you specify the predicate as name and arity, to the second one as a Predicate object. The context module is relevant only for context transparent queries (see the SWI-Prolog Reference Manual). The singleton context module, which is the default parameter, means the context module of the calling context.

Query::~Query();

The destructor closes the query, if this hasn't already been done.

template <typename ForwardIterator>
Query& Query::operator () (ForwardIterator begin, ForwardIterator end);

Query& Query::operator () (Term, Term, Term, Term, Term, Term, Term, Term, Term);
Query& Query::operator () (Term, Term, Term, Term, Term, Term, Term, Term);
Query& Query::operator () (Term, Term, Term, Term, Term, Term, Term);
Query& Query::operator () (Term, Term, Term, Term, Term, Term);
Query& Query::operator () (Term, Term, Term, Term, Term);
Query& Query::operator () (Term, Term, Term, Term);
Query& Query::operator () (Term, Term, Term);
Query& Query::operator () (Term, Term);
Query& Query::operator () (Term);

These operators supply the arguments and start the query. You can either supply the arguments (all Terms) as a STL style range, or directly for up to nine arguments. The result is returned by unifying unbound variables within the argument terms. The first result will be retruned as soon as Query::next() is called for the first time. The operators test if the number of arguments matches the required number, and if another query is still running. In either case, a LogicError is thrown.

If the arity of the predicate is zero, then no call of operator () is needed. The query will be started when next() is called for the first time.

Query& Query::start();

This starts the query without explicitly supplying any arguments. An implicit unbound variable is used for each argument. These implicit arguments can be accessed with Query::operator []. For convenience, start() returns a reference to the same query. So you can do something like q.start().next();.

template <typename ForwardIterator>
Query::Query(const char *, ForwardIterator begin, ForwardIterator end, Module = Module());

Query::Query(const char *, Term, Term, Term, Term, Term, Term, Term, Term, Term, Module = Module());
Query::Query(const char *, Term, Term, Term, Term, Term, Term, Term, Term, Module = Module());
Query::Query(const char *, Term, Term, Term, Term, Term, Term, Term, Module = Module());
Query::Query(const char *, Term, Term, Term, Term, Term, Term, Module = Module());
Query::Query(const char *, Term, Term, Term, Term, Term, Module = Module());
Query::Query(const char *, Term, Term, Term, Term, Module = Module());
Query::Query(const char *, Term, Term, Term, Module = Module());
Query::Query(const char *, Term, Term, Module = Module());
Query::Query(const char *, Term, Module = Module());

template <typename ForwardIterator>
Query::Query(Predicate, ForwardIterator begin, ForwardIterator end, Module = Module());

Query::Query(Predicate, Term, Term, Term, Term, Term, Term, Term, Term, Term, Module = Module());
Query::Query(Predicate, Term, Term, Term, Term, Term, Term, Term, Term, Module = Module());
Query::Query(Predicate, Term, Term, Term, Term, Term, Term, Term, Module = Module());
Query::Query(Predicate, Term, Term, Term, Term, Term, Term, Module = Module());
Query::Query(Predicate, Term, Term, Term, Term, Term, Module = Module());
Query::Query(Predicate, Term, Term, Term, Term, Module = Module());
Query::Query(Predicate, Term, Term, Term, Module = Module());
Query::Query(Predicate, Term, Term, Module = Module());
Query::Query(Predicate, Term, Module = Module());

These constructors create a new query and start it immediately. They have exactly the same effect as constructing the query, using one of the two constructors explained above, followed by a call to Query::operator ().

bool Query::next();

Generate the first or next result. The unifications done in the argument terms for the previous result are always undone. If there is another result, it is retured by (again) unifying the arguments, and true is returned. If there is none, the query is automatically closed and false is returned. Prolog exceptions, raised inside the Prolog engine, are converted to C++ and thrown as PrologExceptions. A LogicError is raised if the query isn't running. Also see the unsafe_exceptions flag.

void Query::close();

Close the query prematurely. All unifications , undoing all unifications done in the arguments. Afterwards the query can be started again. A LogicError is raised if the query isn't running.

void Query::cut();

Closes the query prematurely, but retains the last result. That is, the unifications done in the argument terms are not undone, and the result stays valid. Afterwards the query can be started again. A LogicError is raised if the query isn't running.

Term Query::operator [] (int index);

Return one of the query's arguments. This can only be used while the query is running.

The returned Term is a member of the frame in which the query has been started. It is unified with the corresponding argument supplied to the query. - The query's argument and the Term returned by Query::operator [] point to the same term, and are subject to the same deunification and reunification when the next result is generated. But they may belong to different frames.

The Query::operator [] method checks the range and if the query is running. In case of an error, a LogicError is raised.

bool Query::unsafe_exceptions;

This flag is normally false. If set to true, it causes Query::next() to throw UnsafePrologExceptions instead of PrologExceptions. You normally don't need this flag.

A Prolog exception is a term. Throwing terms as parts of exceptions is a bad idea, because many things can happen to them when the stack is unwound. Most notably, when leaving a frame, they are invalidated. Also, shared subterms could be changed by unification or deunification. Therefore, PrologException stores the term in its external representation, as returned by Term::to_external(). This makes it completely unvolunerable to any hazards. The method as_term() converts the term back, so what it returns is actually a deep copy of the original term.

The safety comes at a small price. First, there is a performance penalty for creating the external representation. Normally exceptions are exceptional, and exception terms are small, so there is no problem. Second, it might be possible, in some pathological design, that indirectly changing the exception term via unification or deunification of shared subterms is intended. This normally isn't the case. Under almost all circumstances, the default behaviour is fine.

The unsafe_exceptions flag gives you full control. UnsafePrologException differs from PrologException in that it does not store the exception term in external form, but includes it as a Term attribute, which is returned by as_term(). This is dangerous.

RecordedTerm

This is an interface to SWI-Prolog's ‘recorded database’. A RecordedTerm object embodies an entry in it. The recorded database exists only while the Prolog engine is running. The objects have a singular state, created by the default constructor or the method clear(), which also exists while the engine is not running. In this state, a RecordedTerm object (logically) holds a term consisting of a free variable. A RecordedTerm may be created only in singular state, as long as the Prolog engine isn't initialized yet. When the engine is shut down, all RecordedTerm objects go to singular state.

RecordedTerm is a model of Equality Comparable, Less Than Comparable and Default Constructible. The comparision operators implement no particular ordering. It is not a model of Assignable, because copying is expensive. RecordedTerms can be copied via the duplicate() method.

RecordedTerm::RecordedTerm();

Initialise the RecordedTerm in singular state, that is, holding a free variable. This does not require the Prolog engine to be running.

RecordedTerm::RecordedTerm(Term);

Make the new RecordedTerm holding a copy of the argument.

Term RecordedTerm::term();
RecordedTerm::operator Term ();

Retrieve a copy of the term stored the recorded database. Each call will create a new deep copy.

void RecordedTerm::operator = (Term t);

Store a new term in this RecordedTerm, deleting the old recorded database entry.

void RecordedTerm::clear ();

Bring the RecordedTerm to singular state, making it hold a free variable. Same as ... = Term(); (but slightly more efficient).

bool RecordedTerm::operator == (const RecordedTerm&) const;
bool RecordedTerm::operator != (const RecordedTerm&) const;

bool RecordedTerm::operator >  (const RecordedTerm&) const;
bool RecordedTerm::operator <  (const RecordedTerm&) const;
bool RecordedTerm::operator >= (const RecordedTerm&) const;
bool RecordedTerm::operator <= (const RecordedTerm&) const;

These implement the concepts Equality Comparable and Less Than Comparable.

void swap(RecordedTerm&);

Make the two RecordedTerms change contents. This is a cheap operation.

void RecordedTerm::duplicate(const RecordedTerm&);

Make the object a deep copy of the other RecordedTerm. This is expensive.

StaticAtom

This is the ‘static’ version of the Atom class. It can be initialised before the Prolog engine is running, and reinitialises itself automatically when the engine is restarted.

StaticAtom::StaticAtom(const char*)

Create a StaticAtom consisting of the supplied text. This string must remain valid. The StaticAtom does not take ownership or copy it. It is meant to be a string literal.

StaticFunctor

This is the ‘static’ version of the Functor class. It can be initialised before the Prolog engine is running, and reinitialises itself automatically when the engine is restarted.

StaticFunctor::StaticFunctor(const char * name, int arity);

Create a StaticFunctor from name and arity. The string must remain valid. The StaticFunctor does not take ownership or copy it. It is meant to be a string literal.

StaticPredicate

This is the ‘static’ version of the Predicate class. It can be initialised before the Prolog engine is running, and reinitialises itself automatically when the engine is restarted.

StaticPredicate::StaticPredicate(const char* name, int arity, const char* module = "user")

Create a StaticPredicate from name, arity and module. The two strings must remain valid. The StaticPredicate does not take ownership or copy them. They are meant to be a string literals.

StaticQuery

This is a subclass of Query, and improves it in the following ways.

  • StaticQuery objects can be created before the Prolog engine is running. The actual initialisation is defered. They reinitialise themselves when the engine is restarted. This makes StaticQuery suitable for objects which reside in static memory, such as global variables.

  • An easy way for specifying queries which have subterms is provided, which makes it exactly like in Prolog.

A StaticQuery can be specified in one of two ways. ‘Predicate mode’ is the way Query works. You specify a predicate, and its arguments are the query arguments.

In ‘Prolog mode’ the query still has a predicate to be called, but its arguments may themselves be terms, with the actual query arguments being free variables inside of them. In this mode, the arguments provided when a new query is started, are filled in there. Only non-anonymous variables (names not starting with ‘<literal>_</literal>’) become query arguments. For instance, the term mypred(foo(bar(X,_,Y)),Z) specifies a query which has three arguments.

Predicate mode is more efficient than Prolog mode, both time and space wise. The StaticQuery can't know if the predicate arguments it constructed last time, are still valid when a new query is started. Therefore, it stores them in a RecordedTerm, and creates a new copy every time. This means, a handfull of possible extra function calls to the Foreign Language Interface, and a few extra terms created, which will later be garbage collected.

StaticQuery::StaticQuery(const char* name, int arity, const char* mod = 0);
StaticQuery::StaticQuery(const char* text, const char* mod = 0);

The first one creates the new StaticQuery in predicate mode, like Query. name and arity are name and arity of the predicate to be called. The second one creates it in Prolog mode. text specifies the query as a term.

mod is the name of the context module (which is relevant only for module transparent predicates). ‘When NULL, the context module of the calling context will be used, or user if there is no calling context (as may happen in embedded systems).’

name, text and mod must stay valid, so they can be used when the Prolog engine is stared or restarted. They are supposed to be string literals. No memory management is done of them by StaticQuery.

template <typename ForwardIterator>
StaticQuery& StaticQuery::operator () (ForwardIterator begin, ForwardIterator end);

StaticQuery& StaticQuery::operator () (Term, Term, Term, Term, Term, Term, Term, Term, Term);
StaticQuery& StaticQuery::operator () (Term, Term, Term, Term, Term, Term, Term, Term);
StaticQuery& StaticQuery::operator () (Term, Term, Term, Term, Term, Term, Term);
StaticQuery& StaticQuery::operator () (Term, Term, Term, Term, Term, Term);
StaticQuery& StaticQuery::operator () (Term, Term, Term, Term, Term);
StaticQuery& StaticQuery::operator () (Term, Term, Term, Term);
StaticQuery& StaticQuery::operator () (Term, Term, Term);
StaticQuery& StaticQuery::operator () (Term, Term);
StaticQuery& StaticQuery::operator () (Term);
StaticQuery& StaticQuery::operator () ();

bool StaticQuery::next();
void StaticQuery::cut();
void StaticQuery::close();

Same as with Query.

StaticTerm

StaticTerm is a subclass of RecordedTerm and provides automatical initalisation. It is a model of Equality Comparable, Less Than Comparable and Default Constructible. The comparision operators implement no particular ordering. It is not a model of Assignable, because copying is expensive. It is still possible via the duplicate() method.

StaticTerm::StaticTerm();

Creates a StaticTerm which holds a free variable.

StaticTerm::StaticTerm(const char*);

Creates a StaticTerm which automatically initialises and reinitialises itself from the string representation provided as the argument, when the Prolog engine is started or restarted. The string is meant to be a string literal. It must remain valid, and there is no memory management performed by StaticTerm.

Changing the term stored as a recorded term via operator = (Term) has no effect on the next reinitialisation of the RecordedTerm. However, a new initialisation string may be provided via operator = (const char*).

void StaticTerm::operator = (const char*)

Replace the initialisation string, such that the new one will be used when the Prolog engine is restarted. The string must remain valid, and its memory isn't managed by the StaticTerm (no ownership taken).

void StaticTerm::swap(StaticTerm&);

Make the two StaticTerms change contents, including the initialisation strings. Fast operation.

void StaticTerm::duplicate(const StaticTerm&);

Make the object a copy of the argument. Same as RecordedTerm::duplicate, but also affects the initialisation string.

Term StaticTerm::term();
operator StaticTerm::Term ();
void StaticTerm::operator = (Term);
void StaticTerm::clear();

bool StaticTerm::operator == (const StaticTerm&) const;
bool StaticTerm::operator != (const StaticTerm&) const;

bool StaticTerm::operator >  (const StaticTerm&) const;
bool StaticTerm::operator <  (const StaticTerm&) const;
bool StaticTerm::operator >= (const StaticTerm&) const;
bool StaticTerm::operator <= (const StaticTerm&) const;

These are the same as for RecordedTerm.

Term

This is the most important class. A Term behaves (mostly) like a pointer into a global graph of term components. This is discussed in detail in the section called “The Global Term Components Graph”.

Term is a model of Assignable and of Default Constructible. After you assign a Term to another one, both point to the same term.

Term also has the comparision operators defined. But it is not a model of either Equality Comparable or Less Than Comparable, because the comparisions operate on the terms pointed to, not on the pointers. (Actually, because of the way SWI-Prolog works, it is not possible to define comparisions which compare the conceptual pointers).

This means that you can't use Term as a parameter for a template which requires it to be a model of Equality Comparable or Less Than Comparable. For instance, Terms can't be used as key for sorted containers. [ If Terms are used in such a way that they have the semantics of terms (not term pointers), then this might still be possible. But you would need to either restrict yourself to ground terms or interpret variables as standing for themselves. Be sure you know what you're doing. ]

Every Term belongs to a frame. Unless initialized in an assignment (by the copy constructor), it always starts in the current frame. If a Term is assigned another one, then it ‘migrates’ to the frame of that term, unless Term::assign() is used.

Term::Term();

Create a new term which consists of a free variable, and make the new Term point to it. It belongs to the current frame.

template <typename ForwardIterator>
Term::Term(const char* functor_name, ForwardIterator begin, ForwardIterator end);

template <typename ForwardIterator>
Term::Term(string functor_name, ForwardIterator begin, ForwardIterator end);

template <typename ForwardIterator>
Term::Term(Functor functor, ForwardIterator begin, ForwardIterator end);

Create a new term from a functor and a STL-style sequence. The type of the values of the sequence can be Term, or any type which can be converted to Term. For the first two constructors, the arity is not needed. It is taken from the length of the sequence. In the third constructor, a LogicError exception is raised of the length of the sequence and the arity of the functor don't match. The new Term belongs to the current frame.

Term(const char* functor, Term, Term, Term, Term, Term, Term, Term, Term, Term);
Term(const char* functor, Term, Term, Term, Term, Term, Term, Term, Term);
Term(const char* functor, Term, Term, Term, Term, Term, Term, Term);
Term(const char* functor, Term, Term, Term, Term, Term, Term);
Term(const char* functor, Term, Term, Term, Term, Term);
Term(const char* functor, Term, Term, Term, Term);
Term(const char* functor, Term, Term, Term);
Term(const char* functor, Term, Term);
Term(const char* functor, Term);

Create a new term from a functor name and the component terms. Be careful to use Term(Atom(txt)), not Term(txt) if you want to create a term which consists of an atom (a 0-ary functor). Otherwise, txt would get parsed as a term.

Term::Term(const long &);
Term::Term(const int &);
Term::Term(const double &);
Term::Term(void * const &);

Create a new term containing the argument, and make the new Term point to it. It belongs to the current frame.

Term::Term(const Atom &);

Create a new term which consists of the atom, and make the new Term point to it.

Term::Term(const Functor &);

Create a new term which consists of the functor and free variables. Make the new Term point to it.

Term::Term(const char *);
Term::Term(const string &);

Parse the text and create a new term from it. If the text is not well-formed, a ParseError exception is raised. Make the new Term point to it. Use Term(Atom(text)) instead, if you wish to create a term which contains an atom which is the text. The new Term belongs to the current frame.

template <typename InputIterator>
Term::Term(InputIterator begin, InputIterator end);

Create a new term containing a Prolog list. The arguments specify an STL-style sequence. The value type of InputIterator must be convertible to Term. The new Term belongs to the current frame.

template <typename TargetType, class OutputIterator>
OutputIterator Term::copy_list(OutputIterator);

Assume the term is a Prolog list. Write its contents to the output iterator, converting each element to TargetType. The template parameter OutputIterator can be infered, but TargetType can't. So this template method is called with one template parameter, like ‘t.copy_list<int>(it)’.

(The target type can't be infered from the iterator type, because STL output iterators don't have value types. The same output iterator can be able to absorb values of several differnt types.)

The following paragraphs are relevant only if you want to use copy_list to convert the element terms to C strings (char* or const char*).

copy_list<> has a different meaning for the target type char* than for other types - it doesn't write the values to the output iterator, but pointers to them. The values themselves are on the heap, and must be deleted later.

The default conversion of terms to char* isn't applicable, because it wouldn't copy the strings to the C++ heap, but leave them in a SWI-Prolog managed ring buffer, which is 16 entries large. Therefore copy_list<> is specialised for the target type char*, such that the strings are copied to the heap. The follwoing is an example of how to use copy_list<>.

void delete_cstr(char* p) { delete p; }

// ...

  Term t = "[this, is, a, list(of(terms))]";
  vector<char*> v;
  t.copy_list<char*>(back_inserter(v));

  cout << "contents of the vector: ";
  copy(v.begin(), v.end(), ostream_iterator<char*>(cout," "));
  cout << endl;

  for_each(v.begin(), v.end(), delete_cstr);

copy_term<> is not specialised for const char*, because copying the strings to the heap and then returning a const pointer, which can't be used to delete it, would result in a memory leak. The default conversion returns pointers which point to memory managed by the SWI-Prolog ring buffer, which is 16 entries large. So only lists up to 16 elements can be converted by copy_list<const char*>().

Term::operator long () const;
Term::operator int () const;
Term::operator double () const;
Term::operator void* () const;

Assume the term consists of a value of the respective type, and return that value. Throw a ConversionError excpetion, if the term can't be converted to the target type.

Term::operator Atom () const;

Assume the term consists of an atom, and return it. Throw a ConversionError exception, if it doesn't consist of an atom.

Term::operator const char* () const;

Convert the term to a string, using Term::to_str() with the default options. The returned pointer points to memory managed by the Prolog engine. If the term consists solely of an atom, then this is static memory which remains valid (at least as long as the atom exists). Otherwise it is in a ring buffer, which will eventually be deallocated. The string must be copied if it is to be used other than short-term. (See PL_get_chars() in the SWI-Prolog Reference Manual - ‘if the data is not static (as from an atom), the data is copied to the next buffer from a ring of 16 buffers’).

There is no Term::operator string, because C++ strings can be created via operator const char* (), and adding an operator string () would make the conversion ambigious. Use Term::to_string() for a slightly more efficient conversion to string.

template <typename T> Term::operator list<T> () const;

Assume the term is a Prolog list of values which can be converted to T. Convert the term to a list<T>. If the term can't be converted this way, a ConversionError exception is thrown.

This template is specialized for char*, copying the strings to the C++ heap instead of using the ring buffer, like the default conversion would do. You are responsible for eventually deleting them. Consider using list<string> instead.

Using operator list<const char*> is probably an error. The strings can't be copied to the heap, because they could not be be deleted via the const char* pointers. They are left in the SWI-Prolog managed ring buffer of 16 entries. Therefore only lists up to 16 elements long can be converted this way. Use operator list<string> instead.

Use Term::copy_list<>() in order to store a Prolog list in a different type of container.

static const unsigned Term::default_cvt_flags = CVT_WRITE|CVT_VARIABLE;

char*  Term::to_str   (unsigned flags = default_cvt_flags) const;
string Term::to_string(unsigned flags = default_cvt_flags) const;

This are the conversions of terms to strings, as provided by the Foreign Language Interface function PL_get_chars(). The flags specify what kinds of terms should be converted. If the term isn't of such a kind, then a CvtError exception is raised. The flags are (mostly...) documented in the SWI-Prolog Reference Manual.

The pointer returned by to_str() points to memory managed by the Prolog engine. If the term consists solely of an atom, then this is static memory which remains valid (at least as long as the atom exists). Otherwise it is in a ring buffer, which will eventually be deallocated. The string must be copied if it is to be used other than short-term. (See PL_get_chars() in the SWI-Prolog Reference Manual - ‘if the data is not static (as from an atom), the data is copied to the next buffer from a ring of 16 buffers’).

Term::Term(const Term& t2);
Term& Term::operator = (const Term& t2);
void Term::swap(Term& t);

These implement the concept Assignable, except for the frame membership. The assignment and the copy constructor make the new term belong to the frame of t2. The swap method makes the two terms change frames. See the section called “Frames and Frame Membership” for a detailed discussion of frame membership.

bool Term::operator == (const Term&) const;
bool Term::operator != (const Term&) const;
bool Term::operator >  (const Term&) const;
bool Term::operator <  (const Term&) const;
bool Term::operator >= (const Term&) const;
bool Term::operator <= (const Term&) const;

These operators compare two terms in standard term order. They do not compare the pointers which the Term objects conceptually are.

template <typename T>
bool Term::unify(const T&) const;

Unify the two terms. Returns true of successful.

Term Term::operator [] (int n) const;

Get the n-th subterm of the term. n starts at zero. Raises a LogicError exception if the term is not compound, or the index is outside the range 0..arity-1.

Functor Term::functor() const;

Get the functor of the term. An atom counts as a functor with arity zero. Raises a ConversionError exception if the term is neither compound nor an atom.

TermType Term::type() const;

Return the type of the term, which is one of the enumeration values of TermType.

void Term::copy();

Make a deep copy of the term, and make the Term object to the new term. If the old term shares any parts with other terms, this will no longer be the case for the copy.

void Term::assign(Term);

t1.assign(t2) makes t1 point to the same term as t2, in such a way that (unlike t1 = t2) t1 does not migrate to the frame of t2. Apart from this, it does the same as an ordinary assignment, provided you guarantee that there exists only one copy of t1. If this is not the case, you will break the pointer semantics of Term. Use with care! See the section called “Frames and Frame Membership” for a detailed discussion.

string Term::to_external();
static Term Term::from_external(string);

The first one converts the term to its external representation, as described in the SWI-Prolog Reference Manual. The second one creates a new term from an external representation. If this fails, a FormatError exception is raised.

 

The returned data [the external representation] has properties that enable storing the data on an external device. It has been designed to make it possible to store Prolog terms fast an compact in an external database. Here are the main features:

  • Independent of session Records can be communicated to another Prolog session and made visible using PL_recorded_external().

  • Binary The representation is binary for maximum performance. The returned data may contain 0-bytes.

  • Byte-order independent The representation can be transferred between machines with different byte-order.

  • No alignment restrictions There are no memory alignment restrictions and copies of the record can thus be moved freely. For example, it is possible to use this representation to exchange terms using shared memory between different Prolog processes.

  • Compact It is assumed that a smaller memory footprint will eventually outperform slightly faster representations.

  • Stable The format is designed for future enhancements without breaking compatibility with older records.

 
--The SWI-Prolog 4.0.9 Reference Manual, in section 5.6.11.2  

TermType

This is an enumeration of the types of a term, as defined by Prolog.

tt_variable a free variable
tt_atom an atom
tt_string a Prolog string
tt_integer an integer
tt_float a floating point number
tt_compound a compound term

ForeignPredicate

Exceptions

struct PlError
{
  virtual Term   as_term() const = 0;
  virtual string message() const = 0;
};

This is the base class of all exceptions raised by SwiProlog-C++. The as_term() method is used to convert the exception to a Prolog exception, which is a term. When it is raised withing a foreign predicate, this is used to hand it over to the Prolog engine. message() produces an error message.

Most of the following exception classes have a place attribute. It contains the method or function in which the exception was raised.

class PrologException : public PlError
{
public:
  PrologException(Term);
  Term   as_term() const;
  string message() const;
};

This is the C++ version of a Prolog exception, which has been raised in a query. It is packaged as a PrologException by Query::next(), and thrown as a C++ exception.

class UnsafePrologException : public PrologException
{
  Term ex;
public:
  UnsafePrologException(Term e) : ex(e) {}

  Term   as_term() const;
  string message() const;

  void PL_raise_exception() {
    PL_raise_exception(ex.lsi);
  }
}

This is thrown instead of PrologException if the unsafe_exceptions flag of Query is used. The method PL_raise_exception() is for experts of the Foreign Language Interface only. Quoting Jan Wielemaker from a mail: “After PL_raise_exception() the term itself is bound to a very old (and thus long living) reserved term-reference as well. This is the term-reference returned by PL_exception().”

struct LogicError : public PlError
{
  const char* place;
  const char* msg;
  LogicError(const char* p, const char* m) : place(p), msg(m) {}
  Term   as_term() const;
  string message() const;
};

This indicates a bug in the program.

struct FormatError : public PlError
{
  const char* place;
  const char* msg;
  FormatError(const char* p, const char* m) : place(p), msg(m) {}
  Term   as_term() const;
  string message() const;
};

This indicates malformed data. It is currently only thrown by Term::from_external().

struct ParseError : public PlError
{
  const char* place;
  const char* text;
  ParseError(const char* p, const char* t) : place(p), text(t) {}
  Term   as_term() const;
  string message() const;
};

An error parsing the specified text, which should contain a term. Thrown by Term::Term(const char*) and Term::Term(string).

struct ConversionError : public PlError
{
  const char* place;
  const char* msg;
  ConversionError(const char* p, const char* m) : place(p), msg(m) {}
  Term   as_term() const;
  string message() const;
};

This indicates a failed conversion between two of SwiProlog-C++s data types; mostly from or to Term. For instance, it will be thrown when trying to convert a compound term to an integer.

struct CvtError : public ConversionError
{
  CvtError(const char* p)
    : ConversionError(p, "not to be converted") {}
  Term   as_term() const;
};

This indicates failure to represent a term as a string. The flags argument of Term::to_str() and Term::to_string() indicates what kinds of terms should be converted. If the given term is not of such a type, then this exception is thrown. XXX

struct GeneralError : public PlError
{
  const char* place;
  const char* msg;
  GeneralError(const char* p, const char* m) : place(p), msg(m) {}
  Term   as_term() const;
  string message();
};

This can only be thrown by prolog_init(), in case of failed initialisation. See the section called “Starting and Restarting the Prolog Engine”.

Chapter 4. Implementation Notes

This chapter discusses various implementation aspects of SwiProlog-C++. It is meant for people who want to maintain or improve it, or just want to understand the source code. Normal users of the interface do not need these notes.

SWI-Prolog Memory Management

  • All new term references are created on the local stack. When a foreign predicate returns, or a frame is left, these references are invalidated. So all terms created have a limited life time. In order to use them as global data on the C++ side, they must be saved somehow.

  • Most actions, such as creating terms, require the prolog engine to run. ...

Semantics of Term Objects

The only way to refer to terms are by term_t values, as provided by the Foreign Language Interface. A term_t is an index on the local stack. The stack cells are like pointers to terms. So a term_t has the semantics of a pointer to pointers to terms (not of a pointer to terms). This means that, as well as the terms pointed to, the local stack cells can change. For instance:

// create two term_t's pointing to the term "42"
term_t t1 = PL_new_term_ref();
term_t t2 = t1;
PL_put_integer(t1, 42);

// change one
PL_put_atom_chars(t1, "hello");

// now both term_t's point to "hello"

The most direct way would be to encapsulate a term_t in a class Term and to define appropriate operators and methods for it. This would give Terms the semantics of being pointers to pointers to terms, too. The programmer would have to manually create stack cells and deal with pointers to them, just like in the C language Foreign Language Interface

This makes working with terms unnecessarily tedious, error prone and complex. It is possible to transparently handle the enclosed term_ts in such a way that Term objects have the semantics of pointers to terms instead, which allows for a much more intuitive, direct programming style. There are two ways to accomplish this.

    The first approach has the advantage that it is efficient. A Term object structurally consists only of a term_t attribute, which is copied and passed around just like in the C interface (all the methods are inline).

    However, it badly interacts with frames. Leaving a frame invalidates stack cells. Since the stack cell a Term uses, changes with each assignment to it, it is hard to predict which Terms wiil be invalidated.

    Term t1;
    {
      Frame f;
      Term t2(42);
      t1 = t2;
    }
    // now t1 _and_ t2 are invalid
    

    Even worse, it's impossible to save references to terms created inside a frame, like you can when using the C interface.

    term_t t1 = PL_new_term_ref();
    
      fid_t f = PL_open_foreign_frame();
      term_t t2 = PL_new_term_ref();
      // ... create data and make t2 point to it ...
    
      // save reference in t1 before t2 is lost, and close frame
      PL_put_term(t1, t2);
      PL_close_foreign_frame(f);
    
    // now t2 is invalid, but t1 points to the data
    

    These frame related problems don't occur with the second approach. But it would make using Term objects quite inefficient. Every time you pass a Term by value, PL_copy_term_ref() is called, and a stack cell is consumed. This would in effect force you to use references or pointers to Terms instead, making you unable to use Terms the way they are supposed to, like pointers.

    It is legal to write to a stack cell, provided the Term holding it is the only one to do so. The changing of the cell will not affect any other Terms as well.

    - nur beim Erzeugen neuer Terme werden Kellerzellen angelegt

    Init Queues

    The “static” classes automatically (re)initialise themselves. In order to be notfied when this is necessary, they derive protectely from the class Init, which makes them members of an init queue.

    Init queue members have two methods, thaw() and freeze(). They get thawed when they need to be initialised, and frozen when they need to save some state. There are two init queues. All members are thawed/frozen on certain occasions.

    • The engine queue is thawed when the Prolog engine has been initialised. This causes the Static... class objects to initialise themselves as well. It is frozen, before it goes down.

    • One frame queue per frame. It gets frozen when the frame goes out of scope. It doesn't cause any thawing. The objects need to check for themselves. This queue is only used by StaticQuery.

    When an object is enqueued in the engine queue, and the engine is already running, it is thawed immediately by calling the thaw() method. The object initialises itself in frozen state. Then it enqueues itself and maybe get thawed immediately.

    This means that the class Init part of the object need to be created last. Its initialisation doesn't take part in its constructor, but is defered, and explicitly called from inside the larger object's constructor.

    The class Init looks like this.

    struct Init
    {
      Init*  next;    // next queue element
      Init** prev;    // pointer to pointer pointing to this element
    
      Init(Defer);    // no initialisation (enqueueing) yet
    
      // initialisation takes place here
      void enqueue(Init** queue);
    
      ~Init();        // dequeue oneself
    
      virtual void thaw() = 0;
      virtual void freeze() = 0;
    
      void thaw_all();     // thaw entire queue starting here
      void freeze_all();   // freeze entire queue starting here
    };
    

    Foreign Predicates

    SWI-Prolog has two methods of calling foreign functions, and two corresponding kinds of signatures. Both of them use the C interface types, such as foreign_t foo(term_t, term_t). We want to interface to functions like bool foo(Term, Term), and translate Prolog exceptions to C++ exceptions and vice versa. So wee need wrapper functions which comply to the C signatures, do the conversions and translations, and call the C++ functions. It does not seem to be possible to determine the name/arity of the predicate being called inside some generic function, so we need one real wrapper function per foreign predicate.

    Such wrapper functions could be automatically generated with the help of templates. The templates would look like this.

    template <bool (*Fkt)()>
    foreign_t fp_helper()
    {
      try {
        return (Fkt() ? TRUE : FALSE);
      } catch (const char* ex) {
        return PL_raise_exception(Term(Atom(ex)).lsi);
      }
    }
    
    template <bool (*Fkt)(Term)>
    foreign_t fp_helper(term_t t1)
    {
      try {
        return (Fkt(Term(t1)) ? TRUE : FALSE);
      } catch (const char* ex) {
        return PL_raise_exception(Term(Atom(ex)).lsi);
      }
    }
    
    template <bool (*Fkt)(Term, Term)>
    foreign_t fp_helper(term_t t1, term_t t2)
    {
      try {
        return (Fkt(Term(t1, t2)) ? TRUE : FALSE);
      } catch (const char* ex) {
        return PL_raise_exception(Term(Atom(ex)).lsi);
      }
    }
    
    // and so on
    

    Alas, GCC bugs prevent this. It seems like it's impossible to take the address of a template function. For now, we use macros instead. We also need to use the other calling convention, which leads to the current implementation.

    Frames

    Only terms (Term and its variants RecordedTerm and StaticTerm) are affected by frames. All the other things (atoms, functors, foreign predicates and so on) live as long as the engine.

    A frame going out of scope has two effects. First, all bindings done inside it are undone. This can potentially affect all terms, but they stay valid. Second, all local stack indices created inside the frame, ald all Terms using them are invalidated. There is an implicit frame, in which every foreign predicate invocation runs. Explicit frames can be created with the Frame class as well.

    There is no way to rescue term references from being invalidated once a frame is entered. You can only create free variables before the

    Leaving a frame invalidates the Term parts of RecordedTerms and StaticTerms. There are no special measures to take for Terms. They just become invalid and must not be used any longer. The other two, however, are supposed to survive.

    An automatic wakeup feature for RecordedTerm shoed not to be feasible, because you can't keep track of which frame the Term part of a RecordedTerm belongs to. The RecordedTerm can't know when its Term part becomes invalid.

    Queries

    The following components are require by the Foreign Language Interface in order to execute a query.

    • a context module identifier

    • an array of term references for the arguments

    • a predicate identifier

    A Query packages just that. The term references array is reused. It does not do any re-initialising like the Static... classes. When the frame in which the query was created is left, the term references, and so the query is invalidated.

    A StaticQuery additionally has the follwoing components.

    • A RecordedTerm attribute callterm. This stores predefined subterms of the query predicate (arguments for an ordinary Query), and has the actual arguments as crosswise bound variables, like args(arg1(foo(X),_),bar(Y), X,Y).

    A StaticQuery reinitialises itself in two differerent ways, and needs to be member of both init queues. Because it can be a subclass of Init only once, the engine queue membership is implemented by an attribute.

    • When the frame is closed, the array of term references for the query arguments is invalidated. The freeze() is used to reset the predargs attribute, so it can later be reinitialised.

    • When the engine is initialised, the StaticQuery object does its full initialisation. When the engine does down, it takes notice, so it can reinitialse later.