Programmer's Guide

This section describes the main functions of the Logic Server. There are two primary interfaces to the LSAPI:

Most of the examples use pseudo-code API functions that correspond to the class methods of the API. The ideas hold for all environments, but each environment has slightly different syntax and might provide additional services. See the environment-specific reference sections for details on exact syntax.

  • Main Entry Points
  • Logic Server Return Codes
  • Prolog Terms
  • String Passing Interface
  • Scope of Logic Server Terms
  • Implementing a Simple Prolog Listener
  • Calling Terms
  • Mapping Prolog Arguments to Host Variables
  • Making Simple Terms
  • Handling Varying Prolog Types
  • Manipulating Structures
  • Manipulating Lists
  • Asserting and Retracting Dynamic Clauses
  • Consulting Prolog Source
  • Running Multiple Engines
  • Writing Extended Predicates
  • Extended Predicate Libraries (LSXs)
  • Capturing Prolog I/O
  • Error Handling
  • Miscellaneous API Functions
  • Main Entry Points

    The simplest host program to Prolog interface is one that initializes the Prolog engine, loads a Prolog logic-base, calls its main/0 predicate and then closes the Prolog engine. To do this requires the following four functions.

    Init(logic_base_name)
    Initializes the Logic Server engine, allocating resources as needed. If logic_base_name is specified, it is used to open a .cfg file of the same name for custom setting of various runtime parameters.
    Init2(.cfg_parameters)
    Initializes the Logic Server engine like Init(), but takes a string containing .cfg parameters instead.
    Load(logic_base_name)
    Loads the compiled Prolog .xpl file for logic_base_name. A .xpl file must always be loaded before executing any Prolog code. This is because a .xpl file includes a copy of alib.plm which implements a portion of the Prolog system software.
    Main() returns TF
    Calls the main/0 predicate of the loaded program.
    Close()
    Releases the memory and files used by the Logic Server engine.

    For example, you can write a simple host language shell that runs the tutorial Prolog program, ducks.xpl. (This example assumes you are in a command-line environment in which the Prolog reads and writes used in ducks work in a reasonable way.)

    ls.Init();
    ls.Load("ducks");
    ls.Main();
    ls.Close();

    Engine ID Parameter

    All of the base Logic Server API functions take as their first argument a Logic Server Engine ID. You can run multiple engines simultaneously, each in their own memory space. Many of the environments shield the Engine ID parameter in the LSAPI. For example, the object-oriented lsapis of C++, Java, .NET and Delphi both encapsulate this parameter within the class definition.

    The Engine ID parameter is not shown for most of the pseudo code examples of this section.

    Logic Server Return Codes

    Many of the API functions return strings, integers, and other implementation-specific data types; the majority return one of two types defined in the API. These are TERM, which is a Prolog term and is set to the value 0 (FALSE) on failure, and TF, which is a boolean true/false return code.

    The C interface returns RC return codes to indicate errors. All the class interfaces throw LSExceptions when an error occurs.

    TF/Term - Success/Failure Return Codes

    The TF return code corresponds to Prolog success or failure. It can have the value TRUE (one), FALSE (zero) (or the implementation-specific boolean constants, true and false). For the C interface, TF can also return an error code. A TF is returned from those functions that:

    For example, the function Call(), which calls a Prolog predicate, starts the Prolog engine and returns either a term that indicates the query succeeded or 0 (FALSE) if it failed. A TF return code also implies that these functions activate the Prolog error handler, as described in the section on error handling.

    The functions that perform unification, indicated by the word 'Unify' in their names, also return a TRUE or FALSE, but do not start the inference engine or the error handler. The unify functions are most often used in writing extended predicates that might succeed or fail based on a unification.

    Because the return code can be TRUE, FALSE or an error, functions that test the return code should explicitly test for TRUE, as in 'if (TRUE == lsCall(...'

    RC-Error Checking Return Codes

    The RC return code is a normal function return code, with OK (zero) being a normal termination and other values representing various error conditions. Check the reference for each function to see if there are error conditions you should be checking for. Many simply return OK all of the time.

    It is always a good idea to check the return codes, because it is often the case that successive calls to the Prolog API depend on the success of previous ones. For example, if the program load failed, then it doesn't make sense to start it running.

    If you continue after an API error, at best your program won't behave as expected, and at worst you might cause it to crash (although we try to guard against this).

    Prolog Terms

    To do any more than simply call the main/0 predicate, it is necessary to pass information back and forth between the host language and Prolog. This is done using Prolog terms.

    All data types in Prolog are represented by a single entity, called a 'term.' For example, each of the following is a Prolog term:

    Terms are represented internally in Prolog by a pointer to the Prolog heap, on which the terms are constructed. These pointers are heavily used in the API in order to transfer information between the host language and Prolog.

    Each of the environments has a data type defined for terms, and most of the API calls have one or more arguments that are either terms or pointers to terms. Check the environment specific reference for details.

    Internals of Complex Terms

    Terms are, in general, built on the Prolog heap, which is an array of 'cells.' Simple terms, such as integers and atoms take up a single cell, whereas complex structures and lists take up multiple cells. Complex terms are made up of simpler terms. To accommodate this, and Prolog unification, cells can contain terms.

    For example, a structure with three arguments will have three cells allocated for those arguments, but each of the arguments might be a term which points to other cells which represent other structures, lists, or any other Prolog data.

    When you use a term in your program, you're really using a cell pointer. The cell might be the entire term or be the start of a chain of cells making up a complex term. You don't need to know this when programming in Prolog or when using the Logic Server's string-passing interface to/ from Prolog, but you do if you're going to construct and dissect complex Prolog terms.

    The Logic Server API provides tools for building terms, calling terms, translating strings to and from terms, decomposing terms, getting host language values from terms, and passing terms to and from Prolog.

    String Passing Interface

    Calling Prolog with a String Query

    Once you have loaded a logic-base (Prolog .xpl file) you can issue Prolog queries and backtrack through all solutions. The API functions designed for string-passing are the simplest to use. The primary functions issue a Prolog query based on an input string. One is for queries that have multiple answers, and one is for queries that will be executed only once.

    CallStr(query_string) returns term
    Convert query_string into a Prolog term pointed to by term_ptr, and call that Prolog term. The term is unified with the results of the query, so all variables which could be unified are unified. If the query succeeds it returns true, and if it fails it returns false.
    ExecStr(query_string) returns term
    Exactly like CallStr, except it is optimized for queries that will not be backtracked through.

    For example, using a classic family tree Prolog application, you might want to issue the query 'sister(mary, X)' to find the sisters of mary. If you entered this query at a Prolog listener

    Prolog would first convert that input to a term, and call it. This is exactly what CallStr() does.

    Normal Prolog execution will cause the term to be unified with clauses in the logic-base, in the process unifying the variable X if there is a match, or failing if there is no match.

    This, too, is what happens with CallStr(). If there is a clause which matches, then the term argument is unified with the result and CallStr() returns that term. If there is no clause which matches, then CallStr() returns a term of 0 (false).

    Converting Terms to Strings

    The Prolog listener automatically displays the bindings of variables for you. The API cannot do this directly, but it does provide you with the tools for manipulating terms. The easiest simply convert the term back into a string.

    TermToStr(term, string, maxlength) returns string
    Convert the term to a string no longer than maxlength characters.
    TermToStrQ(term, string, maxlength) returns string
    Convert the term to a string, using quotes for atoms and strings as necessary, so they can be used as input to Prolog again, if necessary.
    StrTermLen(term) returns length
    Returns the size of the string needed to hold the string representation of the term.

    Continuing the example above, after the 'sister(mary, X)' query was given, you could convert the query term to a string, which might be 'sister(mary, kathy)'. You could then display that string, or parse it using the string manipulation features of the host language.

    Backtracking through Multiple Answers

    Once you have retrieved one answer from a query, you can induce Prolog backtracking and get the next answer. You can do this until there are no more answers using Redo().

    Redo() returns TF
    Using the term pointed to by the previous CallStr, backtrack and redo the query. If the query succeeds, the term is now unified based on this success and Redo() returns true, otherwise it returns false.

    Redo is equivalent to using the semicolon (;) at the Prolog listener to get more answers, or using the 'fail' predicate in a Prolog rule. It returns true or false, depending on whether there was another clause which could be unified with the original query term.

    Putting it All Togther

    The following examples make use of this Prolog program.

    % XGENE.PRO
    
    mother(elaine,mary).
    mother(elaine,kathy).
    mother(elaine,ricky).
    mother(elaine,jenny).
    
    sibling(X,Y) :- mother(P,X), mother(P,Y).

    This pseudo code finds all of the sisters of mary and prints the full terms for each successful answer. (The & symbol indicates an address. It is not necessary to use address operators in all programming environments.)

    declare TERM t
    declare STRING s of length 80
    
    ls.Init()
    ls.Load("xgene")
    
    tf = ls.CallStr(&t, "sibling(mary, X)")
    while (tf == true)
            ls.TermToStr(t, s, 80)
            print(s)
            tf = ls.Redo()
            end while
    
    ls.Close()

    The program will display this output:

    sibling(mary, mary)
    sibling(mary, kathy)
    sibling(mary, ricky)
    sibling(mary, jenny)

    Examples

    C++

    TF tf;
    TERM t;
    char s[80];
    ...
    ls.Init();
    ls.Load("xgene");
    
    t = ls.CallStr("sibling(mary, X)");
    if (t == FALSE) tf = FALSE;
    else tf = TRUE;
    while (tf == TRUE) {
            printf("%s\n", ls.TermToStr(t));
            tf = ls.Redo();
            }
    
    ls.Close();
    ...

    Scope of Logic Server Terms

    When dealing with Call()s there is some question as to the scope of the terms. There are two answers to this question, one safe and one for those wishing to nest Call()s.

    (All references to Call() and Exec() in this article also apply to CallStr() and ExecStr()).

    Common to both is the distinction between Call() and Exec(). Call() builds what is called a choice point on the Prolog control stack. It is this choice point that allows the call to be backtracked into by Redo(). The term built by Call() is reused by Redo(), as can be seen in the documentation for Redo().

    Exec(), on the other hand, does not build a choice point on the control stack, and cannot be backtracked into. It gets one term as an answer.

    So, you should used Exec() if you only require one answer, and Call() if you intend to backtrack through multiple answers.

    Safe Scoping Answer

    You should consider that the API is interfaced to Prolog the same way a Prolog listener is, and that when you do an Call() it is the same as issuing a query at the listener's ?- prompt.

    In the listener, the user can use the ';' key to cause the listener to backtrack and generate more solutions to the query. In the API this same effect is generated by calls to Redo().

    As with the listener, none of the variables or terms from one query presented at a ?- prompt are valid when the next query is posed at a ?- prompt.

    So, when you issue the next Call(), then all of the terms from the previous Call() are no longer in scope. If you try to reference them, you might find them still valid, but this is not guaranteed, and you might also be inviting a GPF.

    Nested Scoping Answer

    Call() builds a choice point on the control stack. That choice point remains there until there are no choices left. That occurs when Redo() is called and fails. So, to clear the choice point from the control stack, you need to execute Redo()s until it returns FALSE.

    If an Call() choice point is still active, you can issue a nested Call(). It is in scope until it finally fails, in which case the Redo() loop for the first Call() can continue.

    This approach requires some care, because, unlike when Prolog is executing its own backtracking search, the programmer must know which query is active when issuing an Redo(). The redo only sends the backtracking message to the most recent active query.

    In addition to calling Redo() until it fails, you can also clear a choice point by calling ClearCall(). Again care must be taken. This will clear the most recent active choice point, so it should only be called when the last Redo() or Call() returned a TRUE and you want to end that choice point.

    The use of ClearCall() can reduce the amount of control stack used by an application that uses Call()s without exhausting all possible answers before going on to the next Call().

    Implementing a Simple Prolog Listener

    Using the tools learned so far, you can implement a simple Prolog listener that prompts the user for the name of a logic-base, loads it and then reads queries from the user, displaying all answers. It will accept almost any query that can be entered at the ?- prompt of the actual Prolog listener, including consults, asserts, retracts and other predicates that manipulate dynamic clauses in the logicbase.

    The pseudo code follows. (The & symbol indicates an address. It is not necessary to use address operators in all programming environments.)

    declare TERM t_query
    declare TF tf
    declare STRING s_input
    declare STRING s_output length 80
    declare STRING s_logic_base
    
    prompt_user("What logic-base to load?")
    s_logic_base = read_user_string()
    ls.Init(s_logic_base)
    ls.Load(s_logic_base)
    
    prompt_user("Enter Prolog query: ")
    s_input = read_user_string()
    while (s_input \= "quit")
            t = ls.CallStr(s_input)
            if (t == FALSE) tf = FALSE;
            else tf = TRUE;
            while (tf == true)
                    s_output = ls.TermToStr(s_output)
                    display(s_output)
                    tf = ls.Redo()
                    end_while
            prompt_user("Enter Prolog query: ")
            s_input = read_user_string()
            end_while
    
    ls.Close()

    Calling Terms

    The two functions, CallStr() and ExecStr() both have counterparts that issue Prolog queries with just terms as input.

    Call(term) returns term
    This calls the term unifying it with clauses in the Prolog logic-base. It is intended for use with Redo() to backtrack through multiple solutions. If backtracking won't be used, then use Exec() instead to avoid unnecessary growth of the Prolog execution control stack.
    Exec(term) returns term
    This calls the term once and returns the result (cannot be followed by Redo()).

    To use either of these, you must have first built a term that represents a Prolog query. The easiest way to do this is with the function that converts a string into a term.

    StrToTerm(string) returns term
    Convert the string to a term and returns it.

    If you wanted, you could replace a call to CallStr() with calls to StrToTerm() and Call(). This obviously doesn't make too much sense for now, but it does illustrate the ability to separate the string to term conversion from the calling of the term.

    Mapping Prolog Arguments to Host Variables

    One way to map Prolog arguments to host language variables is to convert the resultant term into a string, and parse the string. The Logic Server also provides functions that allow for a more direct mapping of Prolog arguments to variables.

    Remember that a Prolog query term is usually a structure, with a functor and a number of arguments. For example, the query 'sibling(mary, X)' is a Prolog term/structure with the functor 'sibling' and two arguments.

    Given this, a function that can retrieve a specific argument from a term/structure and map it into a variable is a very useful one.

    GetFloatArg(term, i_arg) returns double
    Gets the ith argument of term t and returns it as a host language double variable.
    GetIntArg(term, i_arg) returns int
    Gets the ith argument of term t and returns it as a host language double int.
    GetStrtArg(term, i_arg) returns string
    Gets the ith argument of term t and returns it as a host language string.
    lsGetArg(term, i_arg, v_type, var) returns RC
    Gets the ith argument of term t and converts it to host language type v_type and puts the result in variable var.
     

    This is exactly what Get__Arg() does. The first two arguments specify the term and the argument. They return a host language variable type, such as integer or string.

    Get__Arg and GetArgType (below) do the right thing when the Call or Exec had a module qualification. That is, they ignore the :/2 structure used to specify module queries. If you actually are trying to get the args of a :/2 structure, use the special v_types, cMOD, or cGOAL.

    Another useful function returns the type of the argument and is useful when different types of values are returned by the same predicate.

    GetStrArg() can be used to implement a better version of the program that finds mary's sisters. (The & symbol indicates an address. It is not necessary to use address operators in all programming environments.)

    declare TERM t
    declare STRING s of length 80
    declare STRING s_sib
    
    ls.Init("")
    ls.Load("xgene")
    
    t = ls.CallStr("sibling(mary, X)")
    if (t == FALSE) tf = FALSE;
    else tf = TRUE;
    print("Mary's siblings are: ")
    while (tf == true)
            s_sib = ls.GetStrArg(t, 2)
            print(s_sib)
            tf = ls.Redo()
            end while
    
    ls.Close()

    This program will display this output:

    Mary's siblings are:
    mary
    kathy
    ricky
    jenny

    Notice in this example that GetStrArg() has taken the second argument and mapped it into a host language string type variable, but that the Prolog argument was a Prolog atom, not a Prolog string. GetStrArg() and other similar functions will map either Prolog atoms or strings into host language string functions.

    If you want to test the length of the string argument use.

    StrArgLen(term, i_arg) returns integer
    This function assumes the ith argument is a string or atom, and returns its length.

    Example

    C

    #include <stdio.h>
    #include "logicserver.h"
    
    void main()
    {
            ENGid   cureng;
            char    strbuf[80];
            TERM    term;
            int             rc;
            TF              tf;
    
            rc = lsInit(&cureng, "");
            rc = lsLoad(cureng, "xgene");
    
            tf = lsCallStr(cureng, &t, "sibling(mary, X)");
            printf("The siblings of Mary are: ");
            while (tf == TRUE)
                    {
                    rc = lsGetArg(cureng, term, 2, cSTR, strbuf);
                    printf("%s ", strbuf);
                    tf = lsRedo(cureng);
                    }
            printf("\n");
            
            lsClose();
    }

    The output of this programs is:

    Note that, just like in pure Prolog, the variables in the query term are bound (unified) when called, and unbound and rebound by the call to Redo().

    Making Simple Terms

    In addition to the string-based functions, the API provides specific functions to make simple terms. These are:

    MakeAtom(string) returns term
    Returns a Prolog atom term created from the host language string.
    MakeStr(term_ptr, string) returns term
    Returns a Prolog string term created from the host language string.
    MakeInt(term_ptr, int) returns term
    Returns a Prolog integer term created from the host language integer.
    MakeFloat(term_ptr, float) returns term
    Returns a Prolog double precision float term created from the host language double precision float.
    lsMakeAddr(term_ptr, address) returns RC
    Make a term pointing to a Prolog address from a host language pointer. The Prolog address is not a common Prolog term but is useful for passing addresses to Prolog that will at sometime be passed back. For example, a window handle could be passed to Prolog and then passed back by a Prolog predicate that needs to refer to that window.

    Examples:

    declare TERM t
    declare POINTER p
    ...
    t = ls.MakeAtom("hello")
    t = ls.MakeStr("hello")
    t = ls.MakeInt(47)
    t = ls.MakeFloat(4.7)
    ...

    Handling Varying Prolog Types

    Because Prolog isn't typed, you sometimes need to determine the type of term returned to your host language program. GetTermType() performs that function.

    GetTermType(term) returns prolog_type
    The prolog_type is represented by constants which are defined for each particular environment. The prolog_type indicates whether the term is an atom, integer, string, float, structure, list, address or variable.

    If the Prolog term is any thing except a structure, list or variable, you can get the value of the term using Get__Term(). (Structures and lists require more processing, and variables have no value.)

    GetFloatTerm(term) returns double
    Returns the double value of the Prolog term.
    GetIntTerm(term) returns int
    Returns the integer value of the Prolog term.
    GetStrTerm(term) returns string
    Returns the string value of the Prolog term.
    lsGetTerm(term, host_type, address) returns RC
    The Prolog terms is mapped to the host_type of variable pointed to by the address. This is a generic function that can be used for a variety of data types. The host_type is represented by constants defined for each particular environment. This is a generic function that can be used for a variety of data types. The host language type can be an atom, string, integer, long, short, float, double, address, or term.

    Some of these host language types are self-explanatory, such as string, integer. Both double and float refer to double precision floating point numbers (two terms for historical reasons). Some types represent specific Prolog types of information. These are:

    term
    The Prolog term, which is just a pointer, is brought into the host language as is, usually for further processing.
    atom
    The Prolog term is an atom, which is brought in as its atomic number rather than as the string which it represents.
    address
    The Prolog term represents a host language address, which is brought directly in.

    Only certain Prolog terms mix with host language types.

    Prolog type Host language type
    atom string
    atom
    string string
    integer integer
    short
    float
    address address
    variable term
    structure term
    list term

    The following code can be used to indirectly print first "hello" and then the number 49; and directly indicate how term building and retrieval work.

    declare TERM t
    declare INTEGER i
    declare STRING s
    
    t = ls.MakeAtom("hello")
    s = ls.GetStrTerm(t)
    print(s)
    
    t = ls.MakeInt(49)
    i = ls.GetTerm(t)
    print(i)

    Manipulating Structures

    A Prolog structure is composed of a functor and n arguments, where n is the arity of the structure. The following functions allow you to build and decompose Prolog structures.

    MakeFA(term_ptr, string, int) returns term
    Returns a Prolog term representing a structure of arity int whose functor is created from the string. The arguments of the structure are all unbound variables.
    UnifyAtomArg(term, i_arg, string) returns TF
    Unifies (or sets) the ith argument of a structure represented by term, which might have been retrieved from Prolog or might have been constructed using MakeFA(). The argument is unified with the host language string variable.
    UnifyFloatArg(term, i_arg, double) returns TF
    Unifies (or sets) the ith argument of a structure represented by term, which might have been retrieved from Prolog or might have been constructed using MakeFA(). The argument is unified with the host language double variable.
    UnifyIntArg(term, i_arg, int) returns TF
    Unifies (or sets) the ith argument of a structure represented by term, which might have been retrieved from Prolog or might have been constructed using MakeFA(). The argument is unified with the host language integer variable.
    UnifyStrArg(term, i_arg, string) returns TF
    Unifies (or sets) the ith argument of a structure represented by term, which might have been retrieved from Prolog or might have been constructed using MakeFA(). The argument is unified with the host language string variable.
    lsUnifyArg(term_ptr, i_arg, host_type, host_var_addr) returns TF
    Unifies (or sets) the ith argument of a structure represented by term_ptr, which might have been retrieved from Prolog or might have been constructed using MakeFA(). The argument is unified with the host language variable pointed to by host_var_addr of type host_type. Because the host_type can itself be a term, this function can be used to build complex structures that have structures as arguments.

    UnifyArg returns a TF because it might fail when the host language value cannot be unified with the existing Prolog value of the argument, which might be the case if the term being worked on was obtained from Prolog rather than constructed using MakeFA().

    lsGetFA(term, string, int_addr) returns RC
    Retrieves the functor and arity of a term pointing to a structure. The functor is mapped into a host language string and the arity into an integer.
    lsUnify(term1, term2) returns TF
    Unifies the two terms, succeeding or failing as the unification succeeds or fails.

    These functions can be used in conjunction with the string-based functions. For example, these two function calls are equivalent:

    The following example constructs the complex structure foo(bar(one,two),X,Y) using various techniques, and then deconstructs it.

    declare TERM tInner
    declare TERM tStruc
    declare TERM tArg
    declare STRING buf length 80
    declare INTEGER arity
    
    lsMakeFA(&tInner, "bar", 2)
    lsUnifyArg(&tInner, 1, cATOM, "one")
    lsMakeAtom(&tArg, "two")
    lsUnifyArg(&tInner, 2, cTERM, &tArg)
    
    lsMakeFA(&tStruc, "foo", 3)
    lsUnifyArg(&tStruc, 1, cTERM, &tInner)
    
    lsTermToStr(tStruc, buf, 80)
    print("Created structure:")
    print(buf)
    
    lsGetFA(tStruc, buf, &arity)
    print("Got back ")
    print(buf), print("/"), print(arity)
    
    lsGetArg(tStruc, 1, cTERM, &tArg)
    lsTermToStr(tArg, buf, 80)
    print("arg1 = "), print(buf)

    Running this program produces these results, where H10 and H11 represent the variable arguments.

    Created structure: foo(bar(one,two),H10,H11)
    Got back foo/3
    arg1 = bar(one,two)

    Manipulating Lists

    Lists are also pointed to by Prolog terms. The term provides access to the head of the list and the tail, usually another list. Lists are manipulated from the host language just as they are from Prolog, by working from the head of the list.

    Being a term, a list can be built and included in a structure, and, conversely a list can include structures.

    The functions for manipulating lists are

    MakeList() returns term
    Creates an empty list pointed to by list_term.
    GetFloatHead(list_term) returns double
    Returns double value of the head of the list pointed to by list_term. The head of the list remains unchanged.
    GetIntHead(list_term) returns int
    Returns integer value of the head of the list pointed to by list_term. The head of the list remains unchanged.
    GetStrHead(list_term) returns string
    Returns string value of the head of the list pointed to by list_term. The head of the list remains unchanged.
    lsGetHead(list_term, prolog_type, value_ptr) returns RC
    Returns the term of type prolog_type which is the head of the list pointed to by list_term. The head of the list remains unchanged.
    GetTail(list_term) returns term
    Returns the term representing the tail of the list pointed to by list_term.
    PushList(list_term, term) returns term
    Push the term as the head of the list pointed to by list_term. The current value of list_term becomes the tail of the new list, and list_term is returned to point to the new head of the list.
    lsPopList(list_term_ptr, host_type, value_ptr) returns RC
    Pop the head of the list into the host variable pointed to by value_ptr of type host_type. Update list_term_ptr to point to the tail of the list. PopList returns OK (0) as long as there are more elements to be popped from the list.

    Note that lsPopList() updates the term pointed to by list_term_ptr. This allows host language loops to work through lists, either building them or taking them apart. If you want to preserve the head of a list, use Get__Head() or make a copy of the term at list_term_ptr.

    The following example shows how to build a Prolog list from a host language, and then take the list apart.

    declare TERM tList, tHead;
    declare STRING buf length 80
    declare ARRAY[3] of STRING lines = ("first", "second", "third")
    declare INTEGER i
    
    tList = ls.MakeList()
    
    for (i=0; i<3; i=i+1)
            tHead = ls.MakeAtom(lines[i]);
            tList = ls.PushList(tList, tHead);
            end_for_loop
    
    buf = ls.TermToStr(tList)
    write("Made list "), write(buf)
    
    do
            buf = ls.GetStrHead(tList);
            write("Popped "), write(buf)
    while ( (tList=ls.GetTail(tList)) != 0)
    
    lsClose(cureng)

    Running this program produces the results

    Asserting and Retracting Dynamic Clauses

    The Prolog logicbase includes dynamic clauses that have been either consulted or dynamically asserted. A host language program can assert and retract dynamic terms to and from the logicbase for later use by either Prolog or the host language.

    Asserta(term) returns RC
    Assertz(term) returns RC
    Asserts term as either the first (Asserta) or last (Assertz) term recorded under its functor.
    AssertaStr(string) returns RC
    AssertzStr(string) returns RC
    Asserts string as either the first (AssertaStr) or last (AssertzStr) term recorded under its functor.
    Retract(term) returns TF
    Retracts dynamic term from the logicbase.
    RetractStr(string) returns TF
    Retracts dynamic term represented by string s from the logicbase.

    Remember, for your Prolog code you must declare anything asserted to be external if you are using Prolog modules. Otherwise, the compiler will not correctly compile references to the dynamic predicates and an error will occur when attempting to load the program.

    Consulting Prolog Source Code

    You can also consult or reconsult source files of Prolog code. There is no special function for this, but it can be done by simply issuing a Prolog goal to consult or reconsult a file, just as you would from Prolog. You can also load separate modules of compiled Prolog code if desired.

    For example:

    If you wish to use these functions, you must have first loaded any XPL file. This is because .xpl Files are linked with alib.plm, which contains some of Amzi! Prolog's built-in predicates, such as consult, reconsult and load.

    In some cases you might be consulting additional files as part of your application, but in other cases you might wish to consult your main application files rather than compiling them, during development for example. In this case you can use the essentially blank .xpl file, amzi.xpl as the first file to load. This loads the alib predicates for you and lets you use consult for your application files.

    Example

    Multiple Engines

    You can have multiple, independent Prolog engines running at the same time. This feature enables the following types of applications.

    LSAPI - From the LSAPI, you simply call Init() once for each engine. Init() returns the engine ID, which is then used as a parameter in subsequent LSAPI calls. Each engine must be closed individually as well. See the C sample, Pets, for an example.

    C++ - Using the new Logic Server class, LogicServer, you simply create new instances for each engine. The engine ID is managed within the class for you. See the C++ sample, Pets, for an example.

    Multiple Threads - You can run multiple engines simultaneously by starting them in separate threads. See the C++ sample, RubikMT, for an example.

    Delphi - Simply create multiple instances of the Logic Server component. The class maintains the engine ID.

    Java - Simply create multiple instances of the Logic Server class. The class maintains the engine ID.

    .NET - Simply create multiple instances of the Logic Server class. The class maintains the engine ID.

    Visual Basic - The Visual Basic wrapper keeps the current engine ID in a global variable. After initializing an engine, you can retrieve and save its engine ID with GetCurrentEngineLS(). You can then use the engine IDs from different engines to set the current engine using SetCurrentEngineLS().

    Writing Extended Predicates

    To call a host language from Prolog, you must create extended predicates. These behave just like any other built-in Prolog predicates, except you have written them. Extended predicates are entered in the logic base in the default 'user' module.

    Only host languages that support pointers or virtual machine extensions, such as C/C++, Delphi or Java, can be used for implementing extended predicates.

    To add an extended predicate you must:

    1. Define a host language function that will implement the predicate.
    2. Inform the Logic Server during initialization of the name and arity of the Prolog predicate and the address of the host language function that implements it.

    Defining Extended Predicates

    A function that implements an extended predicate takes only one argument, and that is the engine ID of the engine that called the predicate. It can then use that argument when calling other Logic Server functions. (The Logic Server interface for some host language implementations hides the engine ID parameter.)

    To manipulate the parameters of the extended predicate, the function uses a number of Logic Server functions that provide access to the Prolog parameters. These let the function

    These functions provide the flexibility for the extended predicate to behave like a Prolog predicate, that is, it can respond to different types of arguments and behave differently depending on which parameters are bound and which ones are not.

    The function must return a true or false, just as built-in predicates do.

    Like most built-in predicates, extended predicates simply fail on backtracking.

    The following functions are intended for use within extended predicates.

    Notice that they include the word 'Parm' in their names, indicating that they are working with the Prolog parameters list. They are similar to, but not to be confused with, arguments with the word 'Arg' in them, which are used for extracting arguments from Prolog structures.

    GetIntParm(i_parm) returns int
    Gets the ith parameter and returns it as an integer.
    GetFloatParm(i_parm) returns double
    Gets the ith parameter and returns it as a double.
    GetStrParm(i_parm) returns string
    Gets the ith parameter and returns it as a string.
    lsGetParm(i_parm, host_type, var_addr) returns RC
    Gets the ith parameter and puts it in the host variable at var_addr of type host_type.
    GetParmType(i_parm) returns prolog_type
    Returns the Prolog type of the ith parameter.
    StrParmLen(i_parm) returns int
    Returns the length of the string/atom at the ith parameter, so you can decide how to deal with it.
    UnifyAtomParm(i_parm, string) returns TF
    Unifies the ith parameter with the string variable. Because UnifyParm is often used to set a parameter, you should make sure you correctly specify whether you want to use the host type for strings or atoms. Each is represented in the host language as a string, but the Prolog result can be either a Prolog atom or a Prolog string depending on the host type used.
    UnifyFloatParm(i_parm, double) returns TF
    Unifies the ith parameter with the double variable.
    UnifyIntParm(i_parm, int) returns TF
    Unifies the ith parameter with the integer variable.
    UnifyStrParm(i_parm, string) returns TF
    Unifies the ith parameter with the string variable. Because UnifyParm is often used to set a parameter, you should make sure you correctly specify whether you want to use the host type for strings or atoms. Each is represented in the host language as a string, but the Prolog result can be either a Prolog atom or a Prolog string depending on the host type used.
    lsUnifyParm(i_parm, host_type, var_addr) returns TF
    Unifies the ith parameter with the host variable at var_addr of type host_type. Because UnifyParm is often used to set a parameter, you should make sure you correctly specify whether you want to use the host type for strings or atoms. Each is represented in the host language as a string, but the Prolog result can be either a Prolog atom or a Prolog string depending on the host type used.

    Initializing the Extended Predicates

    Once you have defined a number of functions, you need to let the Logic Server know about them. This is done after the call to Init in one of three different ways.

    AddPred(functor, arity, function_ptr)
    Maps the Prolog predicate of functor/arity to the host language function. Must be called once for each extended predicate.
    InitPreds(pred_table_ptr)
    Uses a host language table that maps Prolog predicates to functions. Called once to initialize all of the predicates in the table.
    InitLSX(ptr)
    Causes the Logic Server to check the .cfg file for a lsxload parameter. It then automatically loads any .LSXs (DLLs containing extended predicates) listed in the .cfg file.

    You can use multiple sources of extended predicates, as long as they are all initialized before a .xpl file is loaded. That is, predicate initialization must occur between the call to Init and the call to Load.

    Example

    The following pseudo-code defines three extended predicates that implement simple array-processing in Prolog. (Conventional array processing is not a standard feature in Prolog.). For simplicity, the arrays are one-dimensional and only contain integers. Further there is only simple error checking and the result of an error is simply a return of false. (See the section on error handling for more complex error recovery in extended predicates.)

    The three predicates are

    make_array(ID, Size)
    Initialize a new array of size Size, and returns its ID for use in other calls. Size must be bound to and integer, and ID and unbound variable.
    array_elem(ID, Index, Value)
    Depending on whether Value is an unbound variable or an integer, this predicate will retrieve the Index value of the array or set the value. ID must be bound to a valid array ID, and Index to an integer within the bounds of the array.
    delete_array(ID)
    Free the memory used to allocate the specified array.

    This example illustrates the use of host language addresses as Prolog terms, and the implementation of extended predicates that vary their behavior based on the input arguments.

    make_array is implemented by pMakeArray, array_elem is implemented by pArrayElem, and delete_array is implemented by pDeleteArray. (& indicates an address, which is not needed in all environments.)

    function pMakeArray(eid) returns TF
            declare POINTER to INTEGER iArray
            declare INTEGER iSize
            declare TERM t
    
            lsGetParm(eid, 2, HOST_INT, &iSize)
            iArray = allocate_memory(iSize * sizeof(INTEGER))
            lsMakeAddr(eid, &t, iArray)
            lsUnifyParm(eid, 1, HOST_TERM, &t)
    
            return TRUE
    
    function pArrayElem(eid) returns TF
            declare POINTER to INTEGER iArray
            declare INTEGER ith
            declare INTEGER iElem
            declare TERM t
            declare PROLOG TYPE pt
    
            lsGetParm(eid, 1, HOST_ADDR, &iArray)
            lsGetParm(eid, 2, HOST_INT, &ith)
            pt = lsGetParmType(eid, 3)
            if (pt == PROLOG_INT)
                    lsGetParm(eid, 3, HOST_INT, &iElem)
                    iArray[ith] = iElem
            else if (pt == PROLOG_VAR)
                    lsMakeInt(eid, &t, iArray[ith])
                    lsUnifyParm(eid, 3, HOST_TERM, &t)
            else
                    return FALSE
    
            return TRUE
    
    function pDeleteArray(eid) returns TF
            declare POINTER to INTEGER iArray
    
            lsGetParm(eid, 1, HOST_ADDR, &iArray)
            free_memory(iArray)
    
            return TRUE

    If these functions were part of a host language/Prolog application, they could be initialized like this

    lsAddPred("make_array", 2, pMakeArray)
    lsAddPred("array_elem", 3, pArrayElem)
    lsAddPred("delete_array", 1, pDeleteArray)

    or like this using a table

    declare function pMakeArray(ENGid) returns TF
    declare function pArrayElem(ENGid) returns TF
    declare function pDeleteArray(ENGid) returns TF
    
    declare PRED_INIT table arrayPreds =
            ("make_array", 2, pMakeArray)
            ("array_elem", 3, pArrayElem)
            ("delete_array", 1, pDeleteArray)
            (null, 0, null)
    ...
    lsInitPreds(arrayPreds)

    They could also be included in a .LSX file (see next section), and if they were loaded for use by the Prolog listener, the following Prolog program

    main :-
            make_array(A, 5),
            array_elem(A, 3, 9),
            array_elem(A, 4, 16),
            array_elem(A, 3, X),
            array_elem(A, 4, Y),
            write($element 3 is $), write(X), nl,
            write($element 4 is $), write(Y), nl.

    would produce this output

    Extended Predicate Libraries (LSXs)

    Extended predicates can be included as part of a particular application, or they can be implemented in special DLLs or SOs called .LSXs (Logic Server Extensions). LSXs can be made available to any application, including the IDE. This means you can use your extended predicates directly from the Amzi! IDE.

    An LSX is implemented using the host language tools for generating a DLL or SO. It contains extended predicate definitions as explained in the previous section.

    It contains one additional entry point, which is called by the Logic Server to initialize its predicates. This entry point must be exported by the LSX DLL/SL.

    InitPreds(pointer)
    A user written function, called by the Logic Server, that initializes the functions defined in the .LSX. The pointer argument is not used by the Logic Server, but can be used by the application to pass additional information to the LSX.

    The user implementation of InitPreds() will call either AddPred or InitPreds to initialize the extended predicates.

    Using LSXs

    LSXs can either be loaded using a configuration file parameter, or using a Prolog directive or goal.

    To load using a configuration parameter:

    Include the desired LSX file in the .cfg file in the lsxload parameter. The LSX will be loaded automatically.

    Optionally, you can call InitLSX(pointer), to load the .cfg file named LSXs. This allows you to pass a pointer to the LSXs that can then be used by the LSX. The pointer is set to NULL when the LSXs are loaded automatically.

    The predicates in the LSXs are loaded in the default 'user' module.

    To load from Prolog:

    Use the predicate loadlsx/1. If used in a directive, it will load the predicates into the current module being defined. This allows you to hide extended predicates in a module.

    Example

    The following code can be added to the program implementing array handling predicates, assuming a table defining the predicates.

    function InitPreds(EngID, pointer) returns RC
            lsInitPreds(EngID, arrayPreds)
            return 0

    The program can then be compiled, using the environment-specific tools for creating a dynamic/shared libary (.dll or .so). The library should be renamed to use a .LSX extension.

    Code for the xarray sample is included in the samples/lsx directory with a makefile. The completed LSXs for both are pre-installed in the /amzi/bin directory so they can be used from the IDE by simply turning them on in amzi.cfg.

    To load the xarray.lsx, add this line to amzi.cfg.

    Other applications can either load LSX files from the .cfg file or explicitly load the desired files. To load from the .cfg file, use

    To load explicitly, use

    Capturing Prolog I/O

    In some cases you will want to write a host language shell around Prolog code that captures the normal Prolog I/O streams. (Actually we needed this feature to implement the Windows IDE, but maybe it's of use to you as well.)

    To do this you must

    I/O Functions

    There are two input and two output functions. The output functions correspond to the C functions putc() and puts(). The input functions correspond to the C functions of getc and ungetc.

    The prototypes for your functions should follow this pattern:

    There are some anomalies that you need to be aware of if you intend to mimic the behavior of a Prolog listener.

    You also need to define an extended predicate, keyb/1, that returns keystrokes if you want your Prolog code to use the predicate respkey/1. (See example.)

    Connecting Your Functions

    These API functions let you inform the Logic Server of the functions to use for stream 3 I/O.

    SetInput(my_getc, my_ungetc) returns RC
    Passes the function pointers for your getc and ungetc routines to the Logic Server.
    SetOutput(my_putc, my_puts) returns RC
    Passes the function pointers for your putc and puts routines to the Logic Server.

    Making Stream 3 the Default

    To let Prolog know the I/O is channeled to your functions, you must also set the current stream to the reserved stream, 3, indicating function I/O. This is done using

    SetStream(Stream, ID) returns RC
    Sets the Stream, specified by a defined constant, to the ID specified. The ID is a normal stream ID, that is a small integer. See the defined constants for your environment for the stream specifiers.

    The following statements set all of the I/O streams to use the functions defined for stream 3.

    Once this is done, normal Prolog reads and writes will go through the I/O functions specified in the SetInput and SetOutput functions. (See the Prolog reference for a discussion of Prolog streams.)

    Redirecting I/O Streams Example

    The following example shows how to redirect the I/O streams to run DOS Prolog programs from a Borland EasyWin application.

    #include <stdio.h>
    /* Borland library for getch() */
    #include <conio.h>
    
    /* Define my_getc, my_ungetc, my_putc, my_puts in terms of 
       getchar and putchar. Define predicate keyb/1 using getch().
    */
    
    char unget_buf[128];
    int num_buffered = 0;
    int last_read = 0;
    
    int my_getc()
    {
            if (num_buffered  0) {
                    return (last_read = 
                            unget_buf[num_buffered--]);
    }
            else return (last_read = getchar());
    }
    
    void my_ungetc()
    {
            unget_buf[++num_buffered] = last_read;
    }
    
    void my_puts(const char *s)
    {
            for (int i=0; s[i]; i++) putchar(s[i]);
    }
    
    void my_putc(int c)
    {
            putchar(c);
    }
    
    // Definition for predicate keyb/1
    
    TF p_keyb(ENGid eid)
    {
            int a;
            a = getch();
            if (! lsUnifyParm(eid, 1, cINT, &a))
                    return FALSE;
            return TRUE;
    }

    Error Handling

    Inside the Prolog engine, error handling is implemented using the C++ catch/throw mechanism, integrated with the ISO-standard Prolog catch/throw mechanism. Prolog code can throw errors, which, if uncaught by a Prolog catch, will be caught by the engine's error handler. Likewise, the Prolog catch can be used to catch system errors thrown by the engine.

    There are eight types of errors during Prolog execution.

    Prolog Error Handling

    'Read' and 'exec' errors are passed to the Prolog error handler via a 'throw'. You can handle these errors by using catch/3. The term 'thrown' is of the form:

    Error is either:

    The attribute list contains a list of entries that further describe the error. It may contain any of these entries:

    For example, a read error might throw the following term:

    error(syntax_error,
    [
    type = read,
    rc = 619,
    message = Unexpected operator,
    predicate = read_/2,
    callstack = + fopen/3;- lbLoad/2;- sendInitialForm/0;+ sendInitialForm/0;- once/1;+ cgiMain/0;- once/1;- catch/3;+ catch/3;- main/0;+ main/0;--- top of stack ---,
    read_buffer = if biopsy_performed = $$ no **NEAR HERE** then general = gen_need_biopsy,
    read_file = diagnosis.lb,
    line_number = 145
    ])

    You can see how errors are caught by experimenting in the Prolog listener.

    The attribute list provides flexibility for future enhancements, and can be easily analyzed using a version of the classic member/2 predicate. (There is a copy in the list library, LIST.PLM.) For example, here is the error handling code used in the console version of the Amzi! listener. top$loop is a repeat/fail loop that gets a query in X and calls listen$(X) to interpret it.

    top$loop(Prompt) :-
       ...
       catch(listen$(X), E, except$(E)).
    
    except$(error(Err, Attrs)) :-
      member(rc=RC, Attrs),
      member(type=TYPE, Attrs),
      member(message=MSG, Attrs),
      write(Err), tab(1), write(RC), nl, write(MSG), nl,
      member(predicate=PRED, Attrs),
      write($While executing: $), write(PRED), nl,
      (TYPE==read ->
          member(read_buffer=RB, Attrs),
          write($Read buffer: $), write(RB), nl,
          member(read_file=RF, Attrs),
          (RF == $$ -> true;
            write($File: $), write(RF),
            member(line_number=LN, Attrs),
            write($ Line $), write(LN), nl)
        ; true),
      !,
      what$next(TYPE).
    except$(E) :-
      write($Unexpected Catch: $),
      writeq(E), nl,
      fail.   % fail continues the repeat/fail listener loop.
    
    % what$next - disposition after exception, success ends the listener
    %  repeat/fail loop, failure continues it.
    
    what$next(abort) :-
      !, write($Aborting listener\n$).
    what$next(internal) :-
      !, write($Internal error, aborting listener, contact Amzi! technical support\n$).
    what$next(fatal) :-
      !, write($Prolog resource exhausted, stacks and heaps reinitialized.\n$),
      fail.
    what$next(_) :-
      fail.

    LSAPI Error Handling

    When used from C++ or another object-oriented language, all errors from LSAPI functions are thrown using the LSException class. When used from C, Visual Basic or other procedural languages, the LSAPI functions return error codes.

    You can raise an error (typically from an extended predicate) by using ErrRaise:

    When a return code, either RC or TF, indicates an error occurred or a Logic Server Exception is thrown, there are LSAPI functions that can be used to get additional information about the error. When used from non-object-oriented languages, the function names are called 'GetExcept...' instead of just 'Get...', e.g. GetExceptMsg().

    See the C samples for examples of LSAPI error handling. See logicserver.h for full details of the functions supported.

    Class Error Handling

    When an exception occurs in the engine, and the engine was accessed from the LogicServer class, then an instance of LSException is thrown.

    When catching Logic Server exceptions, it is better to catch a reference to the exception object. For example:

    Class LSException has a number of member functions that let you get details about the exception. See the LSAPI Reference for the LSException methods.

    Break Handling

    A break handler is set and unset upon entry to and exit from Prolog. When the user presses [Ctrl-Break] the Prolog engine stops at the next good stopping point, which is when it's about to call a built-in predicate. The break is treated as an Exec error, so the user can decide what to do next. (Not enabled on the Alpha AXP, breaks simply exit the program.)

    This way, any break handling set up in the host program is active when the host program is in control, and Prolog break handling is active when Prolog is in control.

    Return Codes

    Prolog error handling is established any time you start the Prolog inference engine. The functions which do this are the non-unification functions that return a TF. For example, Call() and Redo() both invoke the inference engine and both return TF. UnifyArg() also returns a TF, but it only uses the unification algorithm, not the full inference engine.

    Miscellaneous Functions

    There are a few functions that pass additional information back and forth between Prolog and C. These are

    GetVersion() returns string
    Returns the current version.
    SetCommandArgs(int argc, char** argv) returns RC
    Pass the command line arguments to Prolog so they can be processed by the command_line/1 Amzi! built-in predicate.

    Copyright ©1987-2011 Amzi! inc. All Rights Reserved. Amzi! is a registered trademark and Logic Server is a trademark of Amzi! inc.