Install/use swi-prolog for C++

I’m new to prolog in C++ (I’ve used the python library before). I have tried to read the documentation, but am pretty lost. I have several questions about the installation and usage of the C++ interface.
(1) How to install swi-prolog? Is there a complete documentation for this?
(2) Is there a simple running example of how to use the prolog interface in C++?

Which OS?

Depends on the OS. For Windows, Mac and Ubuntu it is a common install.

This is still pretty much a work in progress. The best way to keep up on it is to follow the pull request on GitHub.

The “new” C++ API is SWI-cpp2.h is no longer a “work in progress” but is finished. It’s documented here – this might not yet have been refreshed to the very latest, and I need to proof-read it.

Simple examples are in the documentation and also in the files test_cpp.cpp and test_cpp.pl. As @EricGT mentioned, the rocksdb pack is a more complex example: Use more C++ features by kamahen · Pull Request #18 · JanWielemaker/rocksdb · GitHub (I’ve mostly finished modifying the code into a more “natural” C++ style; and I’ve also added some features to the library in the process0>

For Prolog-calling-C++, almost every piece of C functionality has a corresponding piece of C++ functionality. In addition, the C++ API extends the C API by handling C++ exceptions, and by taking advantage of C++'s RAII (Resource Acquisition Is Initialization) to simplify error handling and cleanup. For example, the C++ code for non-deterministic predicates is considerably simpler than the equivalent C code; and the new “blob” interface tries to have suitable defaults for almost everything, so that a “blob” can be defined by just a few lines of boilerplate.

For C++ calling Prolog, the story is a bit less complete, mainly because I don’t currently have much need for it. The basics work fine, but if you need some of the more advanced capabilities, you might need to drop into C. If that’s the case, please open an issue against GitHub - SWI-Prolog/packages-cpp: The SWI-Prolog C++ interface

The discussion around my changes to the C++ API are mostly here: Changes to SWI-cpp.h

1 Like

I’ve started to proof-read the C++ documentation (@jan just pushed SWI-Prolog 9.1.4 with my latest changes) and I realize that the documentation for failure, errors, and exceptions is quite confusing – the design changed considerably while I was working on it (incorporating feedback from users) and the documentation has a lot of out-of-date stuff in it.

For now, test_cpp.cpp and rocksdb4pl.cpp are the best examples; I’ll try to fix the documentation in the next week or two – but I’m not a professional tech writer. If you have questions, please ask in this forum or DM me.

2 Likes

Thanks for your reply. But the example you gave didn’t seem to answer my confusion. Basically, I want to know how the following python functions can be done in C++.

  1. prolog.consult(‘example.pl’)
  2. prolog.query
  3. prolog.assertz
  4. prolog.retract
    Is there an example containing these mappings?

You can find some examples in https://github.com/SWI-Prolog/packages-cpp/blob/97d5ed02656034f64fa47f54a8ed86289a5f043e/test_cpp.cpp , by searching for Query. For example:

PREDICATE(hello, 0)
{ PlQuery q("write", PlTermv(PlTerm_atom("hello hello hello")));
  PlCheckFail(q.next_solution());
  return true;
}

can be easily changed to be your consult('example.pl').

These require being called from Prolog (they’re predicates); if you want to call from C++, look at this section of the manual: SWI-Prolog -- Calling Prolog from C … there are some thin C++ wrappers for this (e.g., PlPredicate for predicate_t, Plx_pred(), Plx_open_query() (the latter is wrapped in class PlQuery).

I should add some examples and test cases, I suppose; but that would require a bit of work because the current test setup is Prolog calling C/C++. There’s also some C code in test_ffi.c (search for PL_open_query … but this is designed for testing some documentation details and doesn’t make great reading.

Sorry, I’m confused. I can’t find a “full” small running example in the code, most of the examples in the documentation are code snippets. For example, I have the following prolog file example.pl
"
fact(1).
fact(2).
fact(3).
"

I followed the documentation SWI-Prolog -- The class PlQuery (version 2)
And if I write the following code.

#include <bits/stdc++.h>
using std::cout;
using std::cin;
using std::endl;

#include <SWI-Prolog.h>
#include <SWI-Stream.h>
#include <SWI-cpp.h>

int main(int argc, char **argv)
{

    PL_initialise(argc, argv);

    PlCall("consult('example.pl')");

    PlTermv av(1);

    PlQuery q("fact", av);
    while( q.next_solution() )
        cout << av[0].as_string() << endl;
    return 0;
}

I would get a compile error

 error: ‘class PlTerm’ has no member named ‘as_string’
1 Like

SWI-cpp2.h corresponds to the “version 2” documentation at SWI-Prolog -- A C++ interface to SWI-Prolog (Version 2)

You also need to include SWI-cpp2.cpp (this probably isn’t documented well - sorry).

Thanks a lot, it helps. I have another question related to assertz. What’s the proper way of calling assertz? When I assertz a fact into the prolog knowledge base, the permission error would appear. The example.pl contains fact(1)…fact(4).
When I tried to insert fact(5) into the knowledge base, the error appears.

int main(int argc, char **argv)
{

    PL_initialise(argc, argv);

    PlCall("consult('example.pl')");

    term_t assertz_query = PL_new_term_ref();
    PL_chars_to_term("assertz(fact(5))", assertz_query);

    PL_call(assertz_query, NULL);

    
    PlTermv av(1);
    PlQuery q("fact", av);
    try {
        while( q.next_solution() ) {
            
            auto res = (char *) av[0];
            int val = atoi(res);
            std::cout << ((char *)av[0]) << std::endl;
        }
    } catch ( PlException &ex ) { 
        std::cerr << (char *) ex << std::endl;
    }
    PL_cleanup(1);
    return 0;
}
1
2
3
4
error(permission_error(modify,static_procedure,fact/1),context(system:assertz/1,_))

You can easily reproduce the error without C++, which feels useless but it shows that the problem is elsewhere:

$ echo 'fact(1).' > foo.pl
$ swipl foo.pl
Welcome to SWI-Prolog (threaded, 64 bits, version 9.1.13)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit https://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

?- assertz(fact(2)).
ERROR: No permission to modify static procedure `fact/1'

You could define fact/1 as dynamic:

$ cat foo.pl 
:- dynamic fact/1.

fact(1).
$ swipl foo.pl
Welcome to SWI-Prolog (threaded, 64 bits, version 9.1.13)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit https://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

?- assertz(fact(2)).
true.

?- fact(X).
X = 1 ;
X = 2.

I should hope this works also from C++ :smiley:

Thanks a lot, it helps.

I’ve noticed that the likes.cpp example code doesn’t compile. Here’s a quick fix to the code: FIXED: likes.cpp sample code by kamahen · Pull Request #51 · SWI-Prolog/packages-cpp · GitHub

A few comments on your code:

  • Most of the PL_*() functions have an equivalent C++ API. E.g.,instead of PL_unify_int64(), you can use PlTerm::unify_integer().
  • If you’re going to use the PL_*() functions, I suggest using the related Plx_*() functions instead. As appropriate, these check the return code and throw a C++ exception if there’s an error. E.g., instead of PL_call(assertz_query, NULL), I suggest Plx_call(assertz_query, nullptr). The documentation for the Plx_*() functions is a bit scanty, partly because in most cases, they don’t need to be called directly.
  • The char* casts are deprecated (you should have got compiler warnings), and replaced by the as_string() methods. Also, (char*) is a C-style cast; for C++, it’s recommended to use static_cast<char*>(...).
  • Use the PlEngine class instead of calling PL_initialise() and PL_cleanup() (or Plx_initialise(), Plx_cleanup()).
  • Instead of term_t assertz_query = Pl_new_term_ref(), you can declare it with PlTerm_var assertz_query.
  • I see that there’s no C++ equivalent of PL_chars_to_term() – I’ll add that. In the meantime, if you define assertz_query the way I suggested, you can call Plx_chars_to_term("assertz(fact(5))", assertz_query.C_).
  • atoi() has been deprecated for many years – use strtol() instead.
  • Instead of catching PlException, catch PlExceptionBase and use what() to get the exception as a string.
2 Likes

Thanks for your reply. I modified the code to version 2. However, there’s a compile error that I don’t know how to fix. Could you please tell me what’s wrong with the code? I think I just followed the version 2 documentation for this. Seems like av[0] is not allowed?

#include <bits/stdc++.h>
#include <SWI-Prolog.h>
#include <SWI-cpp2.h>

void run_query() {
    PlTermv av(1);
    PlQuery q("hello", av);
    try {
        while( q.next_solution() ) {
            
            auto res = av[0].as_string();
            std::cout << res << std::endl;
            int val = atoi(res.c_str());
        }
    } catch ( PlExceptionBase &ex ) { 
        std::cerr << ex.what() << std::endl;
    }
}

// compile command: g++ -std=c++17 trial.cpp -o trial  -L/usr/lib/swi-prolog/lib/x86_64-linux -lswipl

int main(int argc, char **argv)
{
    Plx_initialise(argc, argv);
    PlCall("consult('example.pl')");

    PlCall("assertz(fact(1))");
    PlCall("assertz(fact(2))");
    PlCall("assertz(fact(5))");
    run_query();
    Plx_cleanup(1);
    return 0;
}

The compile error was:

/usr/bin/ld: /tmp/ccGVae1L.o: in function `run_query()':
trial.cpp:(.text+0x9f): undefined reference to `PlTermv::operator[](unsigned long) const'
/usr/bin/ld: trial.cpp:(.text+0xb7): undefined reference to `PlTerm::as_string[abi:cxx11](PlEncoding) const'
/usr/bin/ld: trial.cpp:(.text+0x11a): undefined reference to `PlQuery::next_solution()'
/usr/bin/ld: /tmp/ccGVae1L.o: in function `main':
trial.cpp:(.text+0x2b6): undefined reference to `PlCall(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int)'
/usr/bin/ld: trial.cpp:(.text+0x305): undefined reference to `PlCall(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int)'
/usr/bin/ld: trial.cpp:(.text+0x354): undefined reference to `PlCall(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int)'
/usr/bin/ld: trial.cpp:(.text+0x3a3): undefined reference to `PlCall(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int)'
/usr/bin/ld: /tmp/ccGVae1L.o: in function `void PlEx<int>(int, __PL_queryRef*)':
trial.cpp:(.text._Z4PlExIiEvT_P13__PL_queryRef[_Z4PlExIiEvT_P13__PL_queryRef]+0x21): undefined reference to `PlEx_fail(__PL_queryRef*)'
/usr/bin/ld: /tmp/ccGVae1L.o: in function `unsigned long PlWrap<unsigned long>(unsigned long, __PL_queryRef*)':
trial.cpp:(.text._Z6PlWrapImET_S0_P13__PL_queryRef[_Z6PlWrapImET_S0_P13__PL_queryRef]+0x23): undefined reference to `PlWrap_fail(__PL_queryRef*)'
/usr/bin/ld: /tmp/ccGVae1L.o: in function `__PL_procedure* PlWrap<__PL_procedure*>(__PL_procedure*, __PL_queryRef*)':
trial.cpp:(.text._Z6PlWrapIP14__PL_procedureET_S2_P13__PL_queryRef[_Z6PlWrapIP14__PL_procedureET_S2_P13__PL_queryRef]+0x23): undefined reference to `PlWrap_fail(__PL_queryRef*)'
/usr/bin/ld: /tmp/ccGVae1L.o: in function `__PL_queryRef* PlWrap<__PL_queryRef*>(__PL_queryRef*, __PL_queryRef*)':
trial.cpp:(.text._Z6PlWrapIP13__PL_queryRefET_S2_S1_[_Z6PlWrapIP13__PL_queryRefET_S2_S1_]+0x23): undefined reference to `PlWrap_fail(__PL_queryRef*)'
/usr/bin/ld: /tmp/ccGVae1L.o: in function `void PlEx<bool>(bool, __PL_queryRef*)':
trial.cpp:(.text._Z4PlExIbEvT_P13__PL_queryRef[_Z4PlExIbEvT_P13__PL_queryRef]+0x25): undefined reference to `PlEx_fail(__PL_queryRef*)'
collect2: error: ld returned 1 exit status

You need to add this line:

#include <SWI-cpp2.cpp>

I suggest using something like the following command to compile and link (which will pick up the libraries and also pick up example.pl (which means you won’t need to do the call to consult(example.pl)):

swipl-ld -goal true -o trial -ld g++ -g -O trial.cpp example.pl
1 Like

little side note:

to combine the C++ STL Standard Containers, you do not need to code:
using std::cout;

A simple one liner is enough, to avaid the std::
int the CodeLine: std::cout << “Hello” << std::endl;
if you set a line like this;

#using namespace std;

this using Command includes all std:: Containers (vector, map, cout …).
Happy coding …

I know that this is common, but the Google C++ style guide frowns upon it and I mostly agree with that style guide. Although, to be fair, it’s for code bases with many millions lines of source code.

I know that IDEs can sort this out, but often I read code without the benefit of an IDE (e.g., I’m looking at something in Github).

The Google Python guide also discourages from foo import * for a similar reason, and that seems to be a common opinion in the Python community.

I like to be explicit when I import from Prolog modules also, but that’s a bit more difficult to do because Prolog doesn’t usually use the module:predicate(...) style in code.

1 Like

I use github.com, too.
My actual Project is a mix of some Languages - at current Time, I try me on Prolog implementation.
You can follow the Process there:
privateEditor

it is a one men Project, yet.
But it is target on my fun in coding.

If you have Question or Feedback, drop e message.