Calling Prolog from other languages

The embedded C interface allows you to call Prolog predicates from C or other languages. All arguments to Prolog predicates called this way must be either numeric or passed as pointers. Prolog predicates are declared extern in the C module in which they are used and are declared public in the Prolog module in which they are defined.

In addition, you need to use or modify one of the startup files provided by Arity, either startup.c or apistart.c, to initialize and terminate the Prolog database and stacks before calling Prolog predicates from the other language. As demonstrated in these files, you may also register a handler for errors, aborts and other events as well as access Prolog error strings and environment values.

Public declarations for C callable Prolog predicates

There are three forms of the public declaration used for making predicates callable from external languages. One form is for deterministic (non-backtracking) Prolog predicates, another is for non-deterministic (backtracking) Prolog predicates, and the third is used to make a Prolog predicate callable as a function, where the last argument of the predicate is used a the return value. The formats of the public declarations are as follows:

Deterministic

:- public Name/Arity:pascal(det = Fname(Argtype1,Argtype2, ...)).

Non-deterministic

:- public Name/Arity:pascal(nondet = Fname(Argtype1,Argtype2, ...)).

Function

:- public Name/Arity:pascal(Type = Fname(Argtype1,Argtype2, ...)).

The Name/Arity is the name and arity of the Prolog predicate as it appears in the Prolog module. The Fname is the name of the function as it would be declared in a C or Pascal module. The Argtype specifiers are the arguments to the C function, if any. The Type argument used in the public declaration of a Prolog function is the data type returned by the C function. The Type must be one of the simple data types listed in table 15.1, or a type defined through the use of a typedef declaration (which must have already appeared in the source file).

Note: Recall that the last argument of a Prolog function is used to return the value of the function. Thus, in the public declaration of a Prolog function, the number of Argtype arguments supplied will be one less than the arity listed in the Name/Arity argument since the return value is not an argument in the C function. For instance, for the Prolog function add_it/4, the public declaration will only include three Argtype arguments.

The following are public declarations for deterministic and non-deterministic Prolog predicates and for a Prolog function:

:-public is_from_c/2:pascal(det = is_from_c(cstring, pdouble)).
:- public recorded_from_c/1:pascal(nondet = recorded_from_c(pint)).
:- public count_data/2:pascal(long = count_data(int)).

Note: the public declaration uses the Pascal calling and naming conventions, so Prolog functions called from C must be declared as Pascal functions in the C module:

int far pascal isfromc(cstring, pdouble);

int far pascal recordedfromc(pint);

long far pascal count_data(int);

Handling non-deterministic predicates

As you have read, Arity/Prolog32 predicates called from an external language can be declared as non-deterministic. This means that they have the potential to backtrack Arity/Prolog32 provides two C functions to cause backtracking with these predicates and to limit backtracking, the same way a cut (!) would within Prolog.

redo()

The redo function is used when a call to a non-deterministic Prolog predicate succeeds, to force the Prolog predicate to backtrack. Thus, by calling the redo function in your program after a call to a non-deterministic Prolog predicate, you can get multiple results from a single invocation of the Prolog predicate.

cut()

The cut function is provided for instances when the non-deterministic Prolog predicate succeeds, but you do not want any additional results returned by the predicate through the use of redo. The cut function is called by your C function and is analogous to the use of cut (!) in a Prolog program.

When a Prolog predicate is executed, the variable and expression values are added to the Prolog stack. When a non-deterministic Prolog predicate is executed, Prolog stack space is consumed. This stack space is cleared only when a predicate fails or when the cut function is encountered. Therefore, whenever you call a non-deterministic predicate from your C program, your code should be organized so that one of the following occurs:

  • The non-deterministic predicate should eventually fail, either initially or after a redo.
  • Your C program should include a call to the cut function.

It is possible to nest Prolog calls from C and C calls from Prolog. For instance, you could have a C function which calls a Prolog predicate, which in turn calls another C function, which in turn calls another Prolog predicate, and so on. Such nesting may involve calls to deterministic Prolog predicates and Prolog functions as well as calls to non-deterministic predicates. In such instances, note that any redo or cut functions apply to the most recently called non-deterministic Prolog predicate.

The following is an example of a non-deterministic Prolog predicate bar called from C. The Predicate simply backtracks through the predicate foo and passes the returned value back to C through a pointer:

:- c.
typedef int *pint;

:- prolog.
:- public bar/1:pascal( nondet = bar(pint) ).

bar(Ptr) :-
    foo(X),
    { X:short; Ptr:pint; *Ptr=X }.

foo(1).
foo(3).
foo(5).
foo(7).
foo(12).

The C code to call the predicate bar, printing the returned value until either bar fails or the returned value is greater than 5 is:

#include "apctype.h"
extern int pascal far bar(int *);

bar(){
    int i, value;
    for( i=bar(&value); i!=0; i=redo() )  {
        printf("value =  %d\n",value );
        if (value > 5 ) { cut(); break; };
    };
}

Note: the apctype.h header file is included to provide the declarations for the cut and redo functions. This file contains declarations for all of the C callable routines described in this section, as well as defines macros for the constant values used with these functions.

Starting Prolog from an external language

Two examples of starting Prolog from an external language are provided in the files startup.c and apistart.c. The startup.c file simply initializes the Prolog database and stacks, calls the main/0 predicate and then terminates Prolog. In addition to this functionality, apistart.c also registers an event handler, so that it can capture aborts, breaks and other events and call the predicate restart/0 if appropriate. The apistart.c file is the startup code used in the Arity/Prolog32 Interpreter. This section describes the routines available to you when starting Prolog from an external language. For a complete example of their use, you should examine the startup files. The following is a list of things to notice in the apistart.c file:

  • When you declare main/0 and restart/0 public, the compiler gives these predicates the C callable names prologmain and prologrestart.
  • The variable load_arity must be declared and initialized to supress library code start-up code.
  • You need to use the -Gs switch when compiling to disable stack checking.
  • A registered event handler must ensure that the data segment register is loaded when the function is entered and the previous value is restored when the function exits. If you are compiling with Microsoft C version 6.0 or later you can use the _loadds prefix with the function declaration, otherwise, you need to compile the startup code with the -Au switch.
  • A long jump is used to effect the call to restart/0. When the code is re-entered, the call to initprolog will re-initialize the Prolog system and execution will continue with the restart/0 predicate.

If you modify the startup code to call predicates of your own, then you need to tell the application its "name." You will recall that usually the name of the application is derived from the name of the module containing the main/0 predicate and is then used as a default for the environment and database files. If you do not use a main/0 predicate in your program, you may still want to provide one and declare it public so that the "name" can be found. Alternatively, you can directly specify the name of the environment and/or database files in the initprolog function or you can specify the name with the C variable ENV$. The following is an example of specifying this variable for an application "name" of myapp:

char pascal env$[]="myapp";

The rest of this section describes the functions available for use when starting Prolog from an external language.

int pascal far initprolog(int flags,char far *sptr, long flags2);

This function is used to initialize access to Prolog. It allocates the stack, database cache and overflow file. If the return value is 0 then initialization was successful. Otherwise the return value is an error code. This function must be called before any calls to Prolog are made. The flags2 argument is reserved for use by Arity and must be 0. The value of sptr has different meanings based on the flags value which can be one of the following constants defined in apctype.h:

IP_ENVFILE

sptr is a far pointer to the name of an environment file to be used in initialization.

IP_ENVSTRING

sptr is a far pointer to a string that is treated as the contents of an environment file. Default values are used for any parameters not specified. For example:

"MAXPAGES=20\nLOCAL=32\nGLOBAL=32"

IP_ENVDEFAULT

sptr is ignored. The environment file is taken to have the same name as the Prolog module where main/0 is defined.

IP_STRINGDEF

sptr is considered the same as in the IPENVSTRING case, then the system acts as if IPENVDEFAULT had been specified. This is useful for an application that may want to provide some defaults and still allow the user to specify some environment paramters.

Note: If you are using Arity/Prolog32 for OS/2 and your program is single threaded, then you can add to flags the constant IPSINGLETHREAD. This may boost performance by allowing the system to not worry about multiple threads.

oid pascal far endprolog(void)

The endprolog function is used to terminate access to Prolog. The function releases resources claimed by initprolog and should always be called before exiting the program.

char far * far pascal PrologErrorMessage(int code)

The PrologErrorMessage function returns a pointer to the error message corresponding to the error code. It can then be output by your program using the printf function.

PENVHDLR far pascal PrologRegEvHdlr(PENVHDLR newHandler)

The PrologRegEvHdlr function registers an event handler. The return value is the old event handler. In OS/2 the event handler is specific to each Prolog thread. PENVHDLR is a typedef that is provided in apctype.h. It is defined as:

typedef long(pascal far*PENVHDLR)(int,long,long);

If no event handler has been registered, then the default handler PrologDefEvHdlr is used. You may define your own event handler with a declaration of the following format:

long far pascal _loadds EventHandler(int eventcode,long l1,long l2);

The_loadds is needed if you are not compiling with the -Au switch. Versions of Microsoft C prior to version 6.0 do not support the _loadds keyword so you will need to use the -Au switch. The l2 argument is reserved by Arity. The argument depends on the value supplied in eventcode which can be one of the following constants provided in apctype.h.

EV_FILEERROR

A file error occurred and the error code is passed in the low 16 bits of the l1 argument .

EV_EXERROR

An execution error occurred and the error code is passed in the low 16 bits of the l1 argument

EV_ABORT

The abort/1 predicate was invoked with the value that is passed in the l1 argument.

EV_HALT

The halt/1 predicate was invoked with the value that is passed in the l1 argument.

EV_POISON

The program or thread has been "poisoned" (see the PrologPoisonThread function). The arguments are undefined.

Event handlers need not return to their callers. For instance, the apistart.c file will long jump out of the event handler in order to run the restart/0 predicate. If the event handler returns, it should return a long value where the upper 16 bits is a value dependent on the code supplied in the lower 16 bits, which can be one of the following constants provided in apctype.h.

EVR_ENDPROCESS

The process is ended with an exit code supplied in the upper 16 bits.

EVR_ENDTHREAD

The thread is ended with an exit code supplied in the upper 16 bits.

EVR_CONTINUE

Legal only for EV_ABORT events. If the upper 16 bits are 0 then the abort/1 call will fail, otherwise it succeeds.

long far pascal PrologDefEvHdlr(int eventcode,long l1,long l2)

The PrologDefEvHdlr function is the default event handler used by Arity/Prolog32. If you install your own event handler you may want to call the default handler to process those eventcodes that you do not.

int far pascal PrologPoisonThread(int threadid)

The PrologPoisonThread function can be used within C to stop a Prolog thread as soon as possible and call its event handler. In MS-DOS programs the threadid argument is ignored, as there is only one thread. This function will be called by the system when you type a Control-Break.

Note: Although multiple thread programs can use this function to control a Prolog thread, it is usually better to provide your own thread synchronization.

int far pascal PrologEnvValue(char far *szVarName,void far *pResult)

This function allows you to read the value of Prolog environment parameters. Table 15.3 displays the possible arguments to this function:

szVarName

pResult

MAXPAGES

MINPAGES

XMSPAGES

EMSPAGES

far pointer to an integer value which is the number of pages for the parameter

LOCAL

GLOBAL

MINAVAIL far pointer to an integer value which is the number of kilobytes allocated for the parameter

FILES MAXTHREADS

far pointer to an integer that is the value of the parameter

EMS

XMS

KBDRIVER

far pointer to an integer that is either 1 for yes or 0 for no

OVERFLOW

DATABASE

far pointer to a string which is copied into the buffer you provide. Make sure there is enough space in the buffer.

Table 15.3: PrologEnvValue arguments

The return value is 0 for a successful operation. The return value is non-zero if the variable name is invalid.

Starting multi-threaded Prolog from an external language

An application which uses multi-threaded Prolog code is initialized with the InitPrologProcess function which allocates resources for the shared database. Due to a restriction in the floating point code only one thread can use floating point operations at a time. In a Presentation Manager application it will be common for thread 1 to concern itself with the user interfaces and for other threads to be devoted to time consuming tasks.

Each Prolog thread must have its own local and global data areas. Each area must be a separate segment, allocated with the operating system function DosAllocSeg. Do not try to allocate these areas using the malloc function. Once allocated, these data areas are registered through the PrologInitThread function. Once this has been done, the thread which called the function may call Prolog predicates at will. PrologInitThread may be called again to reset the Prolog data areas. This is useful in handling certain error conditions. Each thread may register its own event handler with the PrologRegEvHdlr function. This event handler is used only for the thread in which it was registered.

When a thread has finished making calls to Prolog it calls the PrologEndThread function. It can then no longer call Prolog. However it may become a Prolog thread again by calling PrologInitThread.

Finally, when you will no longer be calling Prolog code, you need to call PrologEndProcess to deallocate the program resources allocated with PrologInitProcess.

The descriptions of these functions for initializing and terminating Prolog in threads follow. As you may have surmised, in a single threaded system, such as MS-DOS, the functions initprolog and endprolog simply invoke these multi-threaded functions.

int far pascal PrologInitProcess(int flags, long l1, long l2)

The PrologInitProcess function is used to initialize the Prolog database for use within your program. The arguments are the same as those to initprolog. If the return value is 0, the initialization is successful. Otherwise the return value is an error code.

int far pascal PrologInitThread(int flags, SEL local, SEL global, long l)

The PrologInitThread function is used to register local and global data segments for that thread. If the thread had previously called PrologInitThread then the data areas are re-initialized. The arguments for this function have the following meanings:

flags Either 0, or the constant ITFILLAREA. You must use ITFILLAREA if you plan to use the statistics/2 predicate in this thread to get information on the maximum size of local or global areas.

local Selector for the local area

global Selector for the global area

l Reserved, must be 0.

The return value is 0 if successful, otherwise it is an error code, which indicates that the maximum number of Prolog threads is exceeded.

int far pascal PrologEndThread(void)

The PrologEndThread function is used to terminate calling Prolog code from within a thread. The return value is 0 if successful, otherwise it is an error code that indicates that you have not called PrologInitThread in this thread.

void far pascal PrologEndProcess(void)

The PrologEndProcess function closes the Prolog database and releases all resources claimed by PrologInitProcess. Make sure that there are no Prolog threads running when this function is called or your program will die with a protection error.