The Binary Domain

 

Visual Prolog has a special binary domain for holding binary data, as well as special predicates for accessing individual elements of binary terms. The main use for binary terms is to hold data that has no reasonable representation otherwise, such as screen bitmaps and other arbitrary memory blocks. There are separate predicates for reading binary terms from, and writing them to, files. These will be discussed in chapter 12. With the help of the built-in conversion predicate term_bin, conversion from things such as binary file-headers to Prolog terms is a snap, and binary items going into or coming out of foreign language routines are easily handled. Finally arrays may also be implemented easily and efficiently.

Binary terms is a low-level mechanism, whose primary aim is to allow easy and efficient interfacing to other, non-logical, objects, and foreign languages. To this end, binary terms do not behave like other Prolog terms with respect to backtracking. Binary terms will be released if you backtrack to a point previous to their creation, but if you don't backtrack that far any changes done to the term will not be undone. We will illustrate this in the example program at the end of this section.

1. Implementation of binary terms

  Pointer

Size

bytes

     ^   

     |

A binary term is simply a sequence of bytes, preceded by a word (16bit platforms) or dword (32bit platforms), holding its size.

When interfacing to other languages, you should be aware that a term of binary type (the variable passed in the foreign language function call) points to the actual contents, not the size. The Size field includes the space taken up by the field itself. Binary terms are subject to the usual 64K size restriction on 16bit platforms.

2. Text syntax of Binary Terms

Binary terms can be read and written in text format, and also specified in source form in Visual Prolog source code.  The syntax is:

    $[b1,b2,...,bn]

where b1, b2, etc. are the individual bytes of the term. When a binary term is specified in source form in a program, the bytes may be written using any suitable unsigned integral format: decimal, hexadecimal, octal, or as a character. However, the text-representation of binary terms created and converted at run-time is fixed hexadecimal, with no leading "0x" on the individual bytes. Program 8 illustrates this:

/* Program ch11e08.pro */

GOAL
    write("Text form of binary term: ",$['B',105,0o154,0x73,'e',0],'¡¬n').

Load and run this program, and Visual Prolog will respond

    Text form of binary term: $[42,69,6C,73,65,00]

You should hence be careful if you use e.g. readterm to read a binary term at runtime.

3. Creating Binary Terms

Below we discuss the standard predicates Visual Prolog includes, for creation of binary terms.

(1) makebinary/1

makebinary creates and returns a binary term with the number of bytes specified, and sets its contents to binary zero.

    ..., Bin = makebinary(10), ...

The number of bytes should be the net size, excluding the size of the size field.

(2) makebinary/2

makebinary is also available in a two-arity version, allowing specification of an element size.

    ..., USize = sizeof(unsigned), Bin = makebinary(10,USize), ...

This creates a binary term with a size given by the number of elements (10 in the above example) multiplied by the elementsize (sizeof(unsigned) in the above), and sets its contents to zero.

(3) composebinary/2

composebinary creates a binary term from an existing pointer and a length. It's useful in converting pointers to arbitrary blocks of memory returned by foreign language functions. composebinary takes two arguments, and returns a binary.

    ..., Bin = composebinary(StringVar,Size), ...

composebinary takes a copy of the StringVar given as input, so changes to the binary term will not affect StringVar, and vice versa.

(4) getbinarysize/1

getbinarysize returns the net size (in bytes) of the binary term, excluding the size field in front of the data.

    ..., Size = getbinarysize(Bin), ...

4. Accessing Binary Terms

There are eight predicates for accessing binary terms, four for setting entries and four for getting entries. Both groups perform range checking based on the size of the binary term, the index specified, and the size of the desired item (byte, word, dword, or real). It's an error to try to get or set entries outside the range of the binary term.

Take special note that indices (element numbers) are 0-relative; the first element of a binary term has index 0, and the last element of an N-element binary term has index N-1.

(1) getentry/2

getentry is either getbyteentry, getwordentry, getdwordentry, or getrealentry, accessing and returning the specified entry as a byte, word, dword, or real, respectively.

    ..., SomeByte = getbyteentry(Bin,3), ...

(2) setentry/3

setentry is the counterpart to getentry, setting the specified byte, word, dword, or real entry.

    ..., setbyteentry(Bin,3,SomeByte), ...

5. Unifying Binary Terms

Binary terms may be unified just like any other term, in clause heads or using the = predicate:

    ..., Bin1 = Bin2, ...

If either of the terms is free at the time of unification, they will be unified and point to the same binary object. If both are bound at the time of unification, they will be compared for equality.

(1) Comparing Binary Terms

The result of comparing two binary terms is as follows:

If they are of different sizes, the bigger is considered larger; otherwise, they're compared byte by byte, as unsigned values; comparison stops when two differing bytes are found, and the result of their comparison is also the result of the comparison of the binary terms.

For instance, $[1,2] is bigger than $[100], and smaller than $[1,3].

6. Example

Program 9 demonstrates a number of aspects of binary terms.

/* Program ch11e09.pro */

 

CLAUSES

    comp_unify_bin:-

        Bin = makebinary(5),

        comp_unify(Bin,_),

        comp_unify($[1,2],$[100]),

        comp_unify($[0],Bin),

        comp_unify($[1,2,3],$[1,2,4]).

        comp_unify(B,B):-!,

            write(B," = ",B,'¡¬n').

        comp_unify(B1,B2):-

            B1 > B2,!,

            write(B1," > ",B2,'¡¬n').

        comp_unify(B1,B2):-

            write(B1," < ",B2,'¡¬n').

        access(Bin):-

            setwordentry(Bin,3,255),

            fail.              % Changes are not undone when backtracking!

        access(Bin):-

            Size = getbinarysize(Bin),

            X = getwordentry(Bin,3),

            write("¡¬nSize=",Size," X=",X," Bin=",Bin,'¡¬n').

GOAL

    % Illustrate comparison and unification of binary terms

        comp_unify_bin,

 

    % Allocate a binary chunk of 4 words

        WordSize = sizeof(word),

        Bin = makebinary(4,WordSize),

        access(Bin),

 

    % Illustrate range checking; element numbers are 0-relative

        write("Run-time error due to wrong index:¡¬n"),

        Index = 4,

        trap(setwordentry(Bin,Index,0),E,

            write("Error ",E," setting word index ",Index," of ",Bin,'¡¬n')).

This example uses the trap predicate, which will be discussed in the section about error handling below.

7. Converting Terms to Binary Terms

A compound term may have its arguments scattered all over memory, depending on what domains they belong to. Simple types are stored directly in the term record itself, while complex types (those accessed via a pointer, and allocated separately on the global stack) will not necessarily be anywhere near the term they appear in. This is a problem if a term has to be sent out of a program, so to speak, as there is no way make an explicit copy of its contents. Unifying a term variable with another variable will only take a copy of the pointer to the term.

Using term_str (discussed in chapter 13), it is possible to convert the term to a string and back again, but this is rather inefficient when all that's needed is a copy of the term's contents.

term_bin solves this problem.

(1) term_bin/3

term_bin will convert between a term of any domain and a block of binary data, holding the term's contents as well as pointer fixup information. The pointer fixup information will be applied to the binary data when converted back to a term, allowing recreation of any pointers to complex terms the term contains.

term_bin looks like this:

    term_bin(domain,Term,Bin)                  /* (i,i,o) (i,_,i) */

The domain is the domain the Term belongs, or should belong, to, and Bin is a binary term holding the Term's contents.

Example

Program 11 demonstrates conversion between a term and its binary representation. The domains and alignment have been explicitly chosen to ease description, as they would otherwise differ between 16- and 32-bit platforms. Alignment of terms is usually only relevant when interfacing to foreign languages, and is fully described in the chapter 18.

/* Program ch11e11.pro */

If you run this, you'll get:

You shouldn't be too concerned about the actual format of this, in particular as we're dealing with implementation details, which may change. Nevertheless, we'll briefly describe the contents of the binary information:

$[01,07,00,00,00,1F,00,42,69,6C,73,65,00,01,00,00,00,01,00,00,00]
    |  |_________| |___| |_______________| |_________| |_________|
    |          |           |                 |                    |               |
functor
      |          31          "Bilse"¡¬0        offset of     # of ptrs
                |                                            ptr to fix    in fixup
          0-relative                                       (array, but
       
   ptr to string                                   only one element here)

The offset of ptr to fix array will be 16-bit quantities on 16-bit platforms, as will the # of ptrs in fixup.

If the term contains elements from the symbol domain, the binary term will contain additional information to insert the symbols in the symboltable when the term is recreated.

Visual Prolog uses term_bin itself when storing things in the database system and when sending terms over a message pipe to another program. If several programs share external databases or communicate over pipes, it's hence crucial that the domains involved use the same alignment.