Previous Up Next

10.6  Calling Prolog from C

10.6.1  Introduction

The following functions allows a C function to call a Prolog predicate:

void   Pl_Query_Begin        (PlBool recoverable)
int    Pl_Query_Call         (int functor, int arity, PlTerm *arg)
int    Pl_Query_Start        (int functor, int arity, PlTerm *arg,
                              PlBool recoverable)
int    Pl_Query_Next_Solution(void)
void   Pl_Query_End          (int op)
PlTerm Pl_Get_Exception      (void)
void   Pl_Exec_Continuation  (int functor, int arity, PlTerm *arg)
void   Pl_Throw              (PlTerm ball)

The invocation of a Prolog predicate should be done as follows:

The function Pl_Query_Begin(recoverable) is used to initialize a query. The argument recoverable shall be set to PL_TRUE if the user wants to recover, at the end of the query, the memory space consumed by the query (in that case an additional choice-point is created). All terms created in the heap, e.g. using Pl_Mk_... family functions (section 10.4.5), after the invocation of Pl_Query_Begin() can be recovered when calling Pl_Query_End(PL_TRUE) (see below).

The function Pl_Query_Call(functor, arity, arg) calls a predicate passing arguments. It is then used to compute the first solution. The arguments functor, arity and arg are similar to those of the functions handling complex terms (section 10.4.1). This function returns:

The function Pl_Query_Start(functor, arity, arg, recoverable) is a shorthand equivalent to a call to Pl_Query_Begin(recoverable) followed by a call to Pl_Query_Call(functor, arity, arg).

The function Pl_Query_Next_Solution() is used to compute a new solution. It must be only used if the result of the previous solution was PL_SUCCESS. This functions returns the same kind of values as Pl_Query_Call() (see above).

The function Pl_Query_End(op) is used to finish a query. This function mainly manages the remaining alternatives of the query. However, even if the query has no alternatives this function must be used to correctly finish the query. The value of op is:

Note that several queries can be nested since a stack of queries is maintained. For instance, it is possible to call a query and before terminating it to call another query. In that case the first execution of Pl_Query_End() will finish the second query (i.e. the inner) and the next execution of Pl_Query_End() will finish the first query.

The function Pl_Exec_Continuation(functor, arity, arg) replaces the current calculus by the execution of the specified predicate. The arguments functor, arity and arg are similar to those of the functions handling complex terms (section 10.4.1).

Finally the function Pl_Throw(ball) throws an exception. See the throw/1 control construct for more information on exceptions (section 7.2.4). Note that Pl_Throw(ball) is logically equivalent (but faster) to Pl_Exec_Continuation(Pl_Find_Atom("throw"), 1, &ball) .

10.6.2  Example: my_call/1 - a call/1 clone

We here define a predicate my_call(Goal) which acts like call(Goal) except that we do not handle exceptions (if an exception occurs the goal simply fails):

In the prolog file examp.pl:

:- foreign(my_call(term)).

In the C file examp_c.c:

#include <string.h>
#include <gprolog.h>

PlBool
my_call(PlTerm goal)

{
  PlTerm *arg;
  int functor, arity;
  int result;

  arg = Pl_Rd_Callable_Check(goal, &functor, &arity);
  Pl_Query_Begin(PL_FALSE);
  result = Pl_Query_Call(functor, arity, arg);
  Pl_Query_End(PL_KEEP_FOR_PROLOG);
  return (result == PL_SUCCESS);
}

The compilation produces an executable called examp:

% gplc examp.pl examp_c.c

Examples of use:

| ?- my_call(write(hello)).
hello
 
| ?- my_call(for(X,1,3)).
 
X = 1 ?  (here the user presses ; to compute another solution)
 
X = 2 ?  (here the user presses ; to compute another solution)
 
X = 3  (here the user is not prompted since there is no more alternative)
 
| ?- my_call(1).
{exception: error(type_error(callable,1),my_call/1)}
 
| ?- my_call(call(1)).
 
no

When my_call(1) is called an error is raised due to the use of Pl_Rd_Callable_Check(). However the error raised by my_call(call(1)) is ignored and PL_FALSE (i.e. a failure) is returned by the foreign function.

To really simulate the behavior of call/1 when an exception is recovered it should be re-raised to be captured by an earlier handler. The idea is then to execute a throw/1 as the continuation. This is what it is done by the following code:

#include <string.h>
#include <gprolog.h>

PlBool
my_call(PlTerm goal)
{
  PlTerm *args;
  int functor, arity;
  int result;

  args = Pl_Rd_Callable_Check(goal, &functor, &arity);
  Pl_Query_Begin(PL_FALSE);
  result = Pl_Query_Call(functor, arity, args);
  Pl_Query_End(PL_KEEP_FOR_PROLOG);
  if (result == PL_EXCEPTION)
    {
      PlTerm except = Pl_Get_Exception();
      Pl_Throw(except); 
      // equivalent to Pl_Exec_Continuation(Find_Atom("throw"), 1, &except);
    }

  return result;
}

The following code propagates the error raised by call/1.

| ?- my_call(call(1)).
{exception: error(type_error(callable,1),my_call/1)}

Finally note that a simpler way to define my_call/1 is to use Pl_Exec_Continuation() as follows:

#include <string.h>
#include <gprolog.h>

PlBool
my_call(PlTerm goal)
{
  PlTerm *args;
  int functor, arity;

  args = Pl_Rd_Callable_Check(goal, &functor, &arity);
  Pl_Exec_Continuation(functor, arity, args);
  return PL_TRUE;
}

10.6.3  Example: recovering the list of all operators

We here define a predicate all_op(List) which unifies List with the list of all currently defined operators as would be done by: findall(X,current_op(_,_,X),List).

In the prolog file examp.pl:

:- foreign(all_op(term)).

In the C file examp_c.c:

#include <string.h>
#include <gprolog.h>

PlBool
all_op(PlTerm list)
{
  PlTerm op[1024];
  PlTerm args[3];
  int n = 0;
  int result;

  Pl_Query_Begin(PL_TRUE);
  args[0] = Pl_Mk_Variable();
  args[1] = Pl_Mk_Variable();
  args[2] = Pl_Mk_Variable();
  result = Pl_Query_Call(Find_Atom("current_op"), 3, args);
  while (result)
    {
      op[n++] = Pl_Mk_Atom(Pl_Rd_Atom(args[2])); /* arg[2]: the name of the op */
      result = Pl_Query_Next_Solution();
    }
  Pl_Query_End(PL_RECOVER);

  return Pl_Un_Proper_List_Check(n, op, list);
}

Note that we know here that there is no source for exception. In that case the result of Pl_Query_Call and Pl_Query_Next_Solution can be considered as a boolean.

The compilation produces an executable called examp:

% gplc examp.pl examp_c.c

Example of use:

| ?- all_op(L).

L = [:-,:-,\=,=:=,#>=,#<#,@>=,-->,mod,#>=#,**,*,+,+,',',...]

| ?- findall(X,current_op(_,_,X),L).

L = [:-,:-,\=,=:=,#>=,#<#,@>=,-->,mod,#>=#,**,*,+,+,',',...]

Copyright (C) 1999-2021 Daniel Diaz Verbatim copying and distribution of this entire article is permitted in any medium, provided this notice is preserved. More about the copyright
Previous Up Next