Prolog / C data type conversion

Unlike C where strings are handled as pointers to arrays of characters, Prolog treats strings as a unique data type. Therefore they can not be directly cast to one another. The embedded C compiler provides two methods for converting between Prolog and C strings: either through C functions within an embedded C expression or with Prolog predicates. The functional versions are easier to use, but the predicates are provided for compatability. Descriptions of these predicates and functions follow.

make_c_string(+Ptr, +MaxLen, +String, -ActualLen)

The makecstring predicate converts a Prolog string to a C string by copying characters from the Prolog string to a C buffer. If all of the characters have been copied and space remains in the buffer, then a terminating null (0) is added. The Ptr argument is a pointer to the character buffer. Usually Ptr will be a long (far) pointer. However, if Ptr is an integer value that can be represented in 16 bits, then Ptr will be considered a near pointer relative to the default data group. The MaxLen argument is an integer indicating the maximum number of characters that can be copied to the location at Ptr. The String argument is the Prolog string that you want to convert to a C string. The ActualLength argument is returned by the system and indicates the actual length of the string stored, except for the terminating null if it was added.

make_prolog_string(+Ptr, -String)
make_prolog_string(+Ptr, +Length, -String)

The makeprologstring predicates copy a C string to be a prolog string which is unified with the String argument. The Ptr argument has the same meaning as in the makecstring/4 predicate. If Length is specified, then only that number of characters are copied. If Length is not specified, then the C string is assumed to be an asciz string and copying ends at the terminating 0.

int far pascal MakeCString (char far *buf, short sizebuf, short pstring);

The MakeCString function can be called within and embedded C expression an copies the characters from a Prolog string into a previously allocated char buffer pointed to by the buf argument. The maximum size of the buffer is specified in the sizebuf argument. If room remains, a terminating null (0) character is added. The actual number of characters that were copied (except for the terminating null) is passed as the return value for the function. The Prolog string is specified in the pstring argument. This argument is specified as &StringVariable where the StringVariable has been unified with a Prolog string.

int far pascal MakePrologString (char far *buf, short pstring);

The MakePrologString function converts a C char array specified by the buf argument into a Prolog string which is unified with the Prolog variable specified by pstring argument. The pstring argument is specified as &StringVariable, where StringVariable is an uninstantiated Prolog variable. The C string is assumed to be an asciz string so characters are copied until the terminating 0 is found. The number of characters copied is then passed as the return value.

void far pascal MakeCountedPrologString (char far *buf, short len, short pstring);

The MakeCountedPrologString function is similar to the MakePrologString function in that is also converts a C char array specified by the buf argument into a Prolog string which is unified with the Prolog variable specified by the pstring argument. However, the MakeCountedPrologString function does not look for the terminating 0, rather it copies len characters.

An example of converting strings

The following example is extracted from the strbuf.ari example file included with the Arity/Prolog32 distribution. This file defines a number of predicates that use embedded C to access a 128 character buffer which is defined at the beginning of the program:

:- c.
typedef char far *cstring;
char buffer[128];
short buffer_len;                          /* current length of the buffer */
:- prolog.

:- define( bufSIZE = 128).                 /* Macro for the buffer size. */

The function versions of the predicates to store and retrieve a string from this buffer are defined as:

string_buf(String) :-
    string_length(String,SLen0),
    inc(SLen0,SLen),
    ifthenelse( SLen > bufSIZE,
                Len = bufSIZE,
                Len = SLen
    ),
    {
        'MakeCString'(buffer, Len, &String);
        buffer_len = (Len - 1)
    }.

buf_string(String) :-
    { 'MakeCountedPrologString'(buffer, buffer_len, &String) }.

The predicate versions of these predicates would be defined as:

string_buf(String) :-
    string_length(String, SLen),
    ifthenelse( SLen > bufSIZE,
                Len = bufSIZE,
                Len = SLen
    ),
    {
        Ptr:cstring;
        Ptr is buffer
    },
    make_c_string(Ptr, Len, String, _),
    {
        Len:short;
        buffer_len = (Len - 1)
    }.

buf_string(String) :-
    {
        Ptr:cstring;
        Len:short;
        Ptr is buffer;
        Len is buffer_len
    },
    make_prolog_string(Ptr, Len, String).

An example of converting Prolog datatypes

The following is an example of converting data between C and Prolog. The example is contained in the globals.ari file supplied with the Arity/Prolog32 distribution. This file defines two predicates, one for storing (setglobal) and the other for retrieving (getglobal) Prolog terms in 32 global locations allocated in C. These global values are referenced by their position in this array, by specifying an integer in the range 0 through 31. These functions could be used within a program to store a global integer or floating point value, a database reference number of an important term or string, or the term or string itself. The predicates are defined as public and visible so that they may be used within another program or the interpreter:

:- public (set_global/2, get_global/2).
:- visible (set_global/2, get_global/2).

The C declaration space defines a number typedefs that will be needed in embedded C expressions, as well as the global_struct structure that is used to store a global value. The malloc.h C header file is included to do allocation and removal of storage for floating-point numbers, strings and general terms. Since the functions declared by this header file need to be intialized, you must include a C startup with any program that uses these functions. See the following section for information on C startup files.

:- c.
#define MAXGLOBALS 32
typedef char far *cstring;       /* Used in embedded C expressions */
typedef double far *pdouble;
typedef void far *pvoid;
typedef struct {
                 short type;
                 long value;
} global_struct, *global_ptr;
global_struct globals[MAXGLOBALS];
#include "malloc.h"            /* Necessary for strings and floats */
:- prolog.

Each global_struct in the globals array may contain a value. The type of the value is stored in the type member of the structure and can be one of the following constant values. The type indicates how the long value should be interpreted:

:- define( typeINTEGER = 0 ).
:- define( typeATOM    = 1 ).
:- define( typeREF     = 2 ).
:- define( typeSTRING  = 3 ).
:- define( typeFLOAT   = 4 ).
:- define( typeTERM    = 5 ).

The set_global predicate is then very simple, we call an auxilliary predicate which will return the short Type code and long Value that we are storing. Then we assign to a temorary pointer the address of the element of the globals array that we are interested in. Now we examine if there is an allocated type (string, floating-point number or general term) already stored in that location. If so, we need to deallocate it by calling the _ffree function. Then we are free to replace the type and value members with our values:

set_global(Global, Term) :-
    set_global_aux(Term, Type ,Value),
    {
        Type:short,
        Value:long,
        ptr:global_ptr;
        ptr = &(globals .. [Global]),
        ifthenelse( (
            ((ptr ->type) =:= typeSTRING) or
            ((ptr ->type) =:= typeFLOAT) or
            ((ptr ->type) =:= typeTERM)
            ),
            ('_ffree'( pvoid(ptr -> value) ), 1),   /* must have value */
            1
        ),
        ( ptr -> type ) = Type,
        ( ptr -> value ) = Value
    }.

Each clause of setglobalaux determines the type of the given Term, then converts that term into a long Value and returns a type code. Strings, floating-point numbers and terms can not be represented in a long value. Therefore, we need to allocate an appropriate amount of memory, using the _fmalloc function. We then place our Prolog term in the allocated space and return a far pointer to the term as the long value to store in the globals array structure.

Note: general terms are converted to a string using the string_termq predicate so that they will properly convert back.

set_global_aux(Term, typeINTEGER, Value) :-
    integer(Term),
    !,
    Value = Term.
set_global_aux(Term, typeATOM, Value) :-
    atom(Term),
    !,
    {
        Term:'#atom',
        Value:long,
        Value is Term
    }.
set_global_aux(Term, typeREF, Value) :-
    ref(Term),
    !,
    {
        Term:'#ref',
        Value:long,
        Value is Term
    }.
set_global_aux(Term, typeSTRING, Value) :-
    string(Term),
    !,
    string_length(Term, Len),
    {
        Value:cstring;
        Len:short;
        Value is '_fmalloc'(Len+1);
        'MakeCString'(Value, Len+1, &Term)
    }.
set_global_aux(Term, typeFLOAT, Value) :-
    float(Term),
    !,
    {
        Term:double,
        Value:pdouble,
        Value is '_fmalloc'(sizeof(double)),    % note: use of quotes
        (* Value) = Term
    }.
set_global_aux(Term, typeTERM, Value) :-
    string_termq(String, Term),
    set_global_aux(String, _, Value).

The get_global predicate retrieves the Type code and Value from the globals array structure and uses an auxilliary predicate to convert the Value into a term which is then unified with the Term argument. This allows proper unification if Term was bound to a value.

get_global(Global, Term) :-
    {
        (Type,Global):short,
        Value:long,
        ptr:global_ptr,
        ptr is &( globals .. [Global]),
        Type is (ptr -> type),
        Value is (ptr -> value)
    },
    get_global_aux(Type, Value, Term0),
    Term = Term0.

Each clause of getglobalaux converts the long Value into a Prolog Term based on the Type code provided. Strings, floating-point numbers and terms are passed as far pointers to the actual data, so they are not converted directly:

get_global_aux(typeINTEGER, Value, Term) :-
    !,
    Term = Value.
get_global_aux(typeATOM, Value, Term) :-
    !,
    {
        Term:'#atom';
        Value:long;
        Term is Value
    }.
get_global_aux(typeREF, Value, Term) :-
    !,
    {
        Term:'#ref';
        Value:long;
        Term is Value
    }.
get_global_aux(typeSTRING, Value, Term) :-
    !,
    {
        Value:cstring;
        'MakePrologString'(Value, &Term)
    }.
get_global_aux(typeFLOAT, Value, Term) :-
    !,
    {
        Value:pdouble,
        Term:double,
        Term is (*Value)
    }.
get_global_aux(typeTERM, Value, Term) :-
    get_global_aux(typeSTRING, Value, String),
    string_termq(String, Term).