The following functions allows a C function to call a Prolog predicate:
void Pl_Query_Begin (Bool recoverable) int Pl_Query_Call (int functor, int arity, PlTerm *arg) 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)
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 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 Mk_... family functions (section 9.2.5), after the invocation of Pl_Query_Begin() can be recovered when calling Pl_Query_End(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 9.2.1). This function returns:
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.
Finally, 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 9.2.1).
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:
In the C file examp_c.c:
#include <string.h> #include "gprolog.h" Bool my_call(PlTerm goal) { PlTerm *arg; int functor, arity; int result; arg = Rd_Callable_Check(goal, &functor, &arity); Pl_Query_Begin(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:
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 Rd_Callable_Check(). However the error raised by my_call(call(1)) is ignored and 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" Bool my_call(PlTerm goal) { PlTerm *args; int functor, arity; int result; args = Rd_Callable_Check(goal, &functor, &arity); Pl_Query_Begin(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_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" Bool my_call(PlTerm goal) { PlTerm *args; int functor, arity; args = Rd_Callable_Check(goal, &functor, &arity); Pl_Exec_Continuation(functor, arity, args); return TRUE; }
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:
In the C file examp_c.c:
#include <string.h> #include "gprolog.h" Bool all_op(PlTerm list) { PlTerm op[1024]; PlTerm args[3]; int n = 0; int result; Pl_Query_Begin(TRUE); args[0] = Mk_Variable(); args[1] = Mk_Variable(); args[2] = Mk_Variable(); result = Pl_Query_Call(Find_Atom("current_op"), 3, args); while (result) { op[n++] = Mk_Atom(Rd_Atom(args[2])); /* arg #2 is the name of the op */ result = Pl_Query_Next_Solution(); } Pl_Query_End(PL_RECOVER); return 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:
Example of use:
| ?- all_op(L). L = [:-,:-,\=,=:=,#>=,#<#,@>=,-->,mod,#>=#,**,*,+,+,',',...] | ?- findall(X,current_op(_,_,X),L). L = [:-,:-,\=,=:=,#>=,#<#,@>=,-->,mod,#>=#,**,*,+,+,',',...]