The SWI-Prolog.h
header provides various functions for
accessing, setting, and unifying terms, atoms and other types.
Typically, these functions return a 0
(false
)
or
1
(true
) value for whether they succeeded or
not. For failure, there might also be an exception created - this can be
tested by calling PL_excpetion(0).
There are three major groups of methods:
The “put” operations are typically done on an uninstantiated term (see the PlTerm_var() constructor). These are expected to succeed, and typically raise an exception failure (e.g., resource exception) - for details, see the corresponding PL_put_*() functions in Constructing Terms.
For the “get” and “unify” operations, there are three possible failures:
false
return code
Each of these is communicated to Prolog by returning false
from the top level; exceptions also set a “global” exception
term (using PL_raise_exception()). The C++ programmer usually
doesn't have to worry about this; instead they can throw PlFail()
for failure or throw PlException()
(or one of PlException
’s
subclasses) and the C++ API will take care of everything.
These are deprecated and replaced by the various as_*()
methods.
PlTerm
can be converted to the following types:
long
if the PlTerm
is a Prolog
integer or float that can be converted without loss to a long. Throws a
type_error
exception otherwise.long
, but might represent fewer bits.PlTerm
represents a
Prolog integer or float.CVT_ALL|CVT_WRITE|BUF_RING
, which implies Prolog atoms and
strings are converted to the represented text. All other data is handed
to write/1. If
the text is static in Prolog, a direct pointer to the string is
returned. Otherwise the text is saved in a ring of 16 buffers and must
be copied to avoid overwriting.
In addition, the Prolog type (PL_VARIABLE
,
PL_ATOM
, ... PL_DICT
) can be determined using
the type() method. There are also boolean methods that check the
type:
See also section 1.13.1.
*blob==nullptr
.
A family of unification methods are defined for the various Prolog
types and C++ types. Wherever string
is shown, you can use:
char*
whar_t*
std::string
std::wstring
Here is an example:
PREDICATE(hostname, 1) { char buf[256]; if ( gethostname(buf, sizeof buf) == 0 ) return A1.unify_atom(buf); return false; }
An alternative way of writing this would use the PlCheckFail() to raise an exception if the unification fails.
PREDICATE(hostname2, 1) { char buf[256]; PlCheckFail(gethostname(buf, sizeof buf) == 0); PlCheckFail(A1.unify_atom(buf)); return true; }
Of course, in a real program, the failure of
gethostname(buf)sizeof buf should create an error term than
contains information from errno
.
PlTerm
to a long
and perform standard
C-comparison between the two long integers. If PlTerm
cannot be converted a type_error
is raised.true
if the PlTerm
is an atom or string
representing the same text as the argument, false
if the
conversion was successful, but the strings are not equal and an
type_error
exception if the conversion failed.Below are some typical examples. See section 1.11.12.2 for direct manipulation of atoms in their internal representation.
A1 < 0 | Test A1 to hold a Prolog integer or float that can be transformed lossless to an integer less than zero. |
A1 < PlTerm(0) | A1
is before the term‘0’in the‘standard order of terms’.
This means that if A1 represents an atom, this test yields true . |
A1 == PlCompound("a(1)") | Test A1
to represent the term
a(1) . |
A1 == "now" | Test A1 to be an atom or string holding the text “now” . |
Compound terms can be viewed as an array of terms with a name and
arity (length). This view is expressed by overloading the
operator.
[]
A type_error
is raised if the argument is not compound
and a
domain_error
if the index is out of range.
In addition, the following functions are defined:
PlTerm
is a compound term and arg is
between 1 and the arity of the term, return a new PlTerm
representing the arg-th argument of the term. If PlTerm
is
not compound, a
type_error
is raised. Id arg is out of range, a
domain_error
is raised. Please note the counting from 1
which is consistent to Prolog's arg/3
predicate, but inconsistent to C's normal view on an array. See also
class PlCompound
. The following example tests x
to represent a term with first-argument an atom or string equal to gnat
.
..., if ( x[1] == "gnat" ) ...
const char *
holding the name of the functor of
the compound term. Raises a type_error
if the argument is
not compound.type_error
if the argument is not compound.
t.is_null()
is the
same as t.unwrap() == PlTerm::null
t.not_null()
is the
same as t.unwrap() != PlTerm::null
t.reset()
is the same
as t.unwrap() = PlTerm::null
t.reset(x)
is the same as t.unwrap() = x
PL_VARIABLE
, PL_FLOAT
, PL_INTEGER
,
PL_ATOM
, PL_STRING
or PL_TERM
To avoid very confusing combinations of constructors and therefore
possible undesirable effects a number of subclasses of PlTerm
have been defined that provide constructors for creating special Prolog
terms. These subclasses are defined below.
A SWI-Prolog string represents a byte-string on the global stack. Its
lifetime is the same as for compound terms and other data living on the
global stack. Strings are not only a compound representation of text
that is garbage-collected, but as they can contain 0-bytes, they can be
used to contain arbitrary C-data structures. However, it is generally
preferred to use blobs for storing arbitrary C-data structures (see also PlTerm_pointer(void
*ptr)
).
Character lists are compliant to Prolog's atom_chars/2 predicate.
The PlCompound
class is a convenience class for creating
a term from a string; it is similar to (=..)/2
syntax_error
exception is raised. Otherwise a new
term-reference holding the parsed text is created.PlTermv
for details. The example below
creates the Prolog term hello(world)
.
PlCompound("hello", PlTermv(PlAtom("world")))
The class PlTerm_tail
27This
was named PlTail
in version 1 of the API. is
both for analysing and constructing lists. It is called PlTerm_tail
as enumeration-steps make the term-reference follow the “tail” of
the list.
PlTerm_tail
is created by making a new term-reference
pointing to the same object. As PlTerm_tail
is used to
enumerate or build a Prolog list, the initial list
term-reference keeps pointing to the head of the list.PlTerm_tail
reference point to the new variable tail. If A is a variable,
and this function is called on it using the argument "gnat"
,
a list of the form [gnat|B]
is created and the PlTerm_tail
object now points to the new variable B.
This function returns true
if the unification succeeded
and
false
otherwise. No exceptions are generated.
The example below translates the main() argument vector to Prolog and calls the prolog predicate entry/1 with it.
int main(int argc, char **argv) { PlEngine e(argv[0]); PlTermv av(1); PlTerm_tail l(av[0]); for(int i=0; i<argc; i++) PlCheckFail(l.append(argv[i])); PlCheckFail(l.close()); PlQuery q("entry", av); return q.next_solution() ? 0 : 1; }
[]
and returns the
result of the unification.PlTerm_tail
and advance
PlTerm_tail
. Returns true
on success and false
if
PlTerm_tail
represents the empty list. If PlTerm_tail
is neither a list nor the empty list, a type_error
is
thrown. The example below prints the elements of a list.
PREDICATE(write_list, 1) { PlTerm_tail tail(A1); PlTerm_var e; while(tail.next(e)) cout << e.as_string() << endl; return tail.close(); }
The class PlTermv
represents an array of
term-references. This type is used to pass the arguments to a foreign
defined predicate, construct compound terms (see
PlTerm::PlTerm(const char *name)PlTermv arguments ), and to
create queries (see PlQuery
).
The only useful member function is the overloading of
,
providing (0-based) access to the elements. Range checking is performed
and raises a []
domain_error
exception.
The constructors for this class are below. Note that these can be
error-prone because there's no distinction between term_t
and
size_t
; the form of the constructor is determined by
whether the first argument is an integer (term_t
or size_t
)
or
PlTerm
.
load_file(const char *file) { return PlCall("compile", PlTermv(PlAtom(file))); }
If the vector has to contain more than 5 elements, the following construction should be used:
{ PlTermv av(10); av[0].put_term(PlTerm_atom("hello")); av[1].put_term(PlTerm_integer(666)); ... }
Important: be sure that all the arguments are of type
PlTerm
- PlTermv(i)
is not the same as
PlTermv(PlTerm_integer(i))
, and will result in a
runtime error.
Both for quick comparison as for quick building of lists of atoms, it is desirable to provide access to Prolog's atom-table, mapping handles to unique string-constants. If the handles of two atoms are different it is guaranteed they represent different text strings.
Suppose we want to test whether a term represents a certain atom, this interface presents a large number of alternatives:
Example:
PREDICATE(test, 1) { if ( A1 == "read" ) ...; }
This writes easily and is the preferred method is performance is not critical and only a few comparisons have to be made. It validates A1 to be a term-reference representing text (atom, string, integer or float) extracts the represented text and uses strcmp() to match the strings.
Example:
static PlAtom ATOM_read("read"); PREDICATE(test, 1) { if ( A1 == ATOM_read ) ...; }
This case raises a type_error
if A1 is not an
atom. Otherwise it extacts the atom-handle and compares it to the
atom-handle of the global PlAtom
object. This approach is
faster and provides more strict type-checking.
Example:
static PlAtom ATOM_read("read"); PREDICATE(test, 1) { PlAtom a1(A1); if ( a1 == ATOM_read ) ...; }
This approach is basically the same as section 1.11.12.2, but in nested if-then-else the extraction of the atom from the term is done only once.
Example:
PREDICATE(test, 1) { PlAtom a1(A1); if ( a1 == "read" ) ...; }
This approach extracts the atom once and for each test extracts the represented string from the atom and compares it. It avoids the need for global atom constructors.
atom_t
). Used
internally and for integration with the C-interface.type_error
is thrown.true
if the atom represents text, false
otherwise. Performs a strcmp() or similar for this.true
or
false
. Because atoms are unique, there is no need to use strcmp()
for this.==
operator.true
.char*
from a function, you should not
do return t.as_string().c_str()
because that will return a pointer into the stack (Gnu C++ or Clang
options -Wreturn-stack-address
or -Wreturn-local-addr
)
can sometimes catch this, as can the runtime address sanitizer
when run with detect_stack_use_after_return=1
.
This does not quote or escape any characters that would need to be
escaped if the atom were to be input to the Prolog parser. The possible
values for enc
are:
EncLatin1
- throws an exception if cannot be
represented in ASCII.EncUTF8
EncLocale
- uses the locale to determine the
representation.
The recorded database is has two wrappers, for supporting the internal records and external records.
Currently, the interface to internal records requires that
the programmer explicitly call the dupicate() and erase()
methods - in future, it is intended that this will be done automatically
by a new
PlRecord
class, so that the internal records behave like “smart
pointers” ; in the meantime, the PlRecord
provides a
trivial wrapper around the various recorded database functions.
The class PlRecord
supports the following methods:
PlRecord
object.
The class PlRecord
provides direct access to the
reference counting aspects of the recorded term (through the duplicate()
and
erase() methods), but does not
connect these with C++'s copy constructor, assignment operator, or
destructor. If the recorded term is encapsulated within an object, then
the containing object can use the duplicate()
and erase() methods in its copy and
move constructors and assignment operators (and the erase()
method in the destructor).29The
copy constructor and assignment use the duplicate()
method; the move constructor and assignment use the duplicate()
method to assign to the destination and the erase()
method on the source; and the destructor uses erase().
Alternatively, the std::shared_ptr
or std::unique_ptr
can be used with the supplied PlrecordDeleter
, which calls
the
erase() method when the shared_ptr
reference count goes to zero or when the std::unique_ptr
goes out of scope.
For example:
std::shared_ptr<PlRecord> r(new PlRecord(t.record()), PlRecordDeleter()); assert(t.unify_term(r->term()));
The class PlRecordExternalCopy
keeps the external
record as an uninterpreted string (which may contain nulls). It
supports the following methods.