TAP Manual.

TAP, Thick Ada-Prolog bindings.

version 0.2, August 2002

Alexander Kopilovitch
aek@acm.org,   aek@vib.usr.pu.ru

Table of Contents

0. Conventions.

Throughout this Manual the following conventions are used:

   subtype VString is Ada.Strings.Unbounded.Unbounded_String;
   Nul : VString renames Ada.Strings.Unbounded.Null_Unbounded_String;
Also, throughout this Manual a predicate's instance (in a Prolog program) means a dynamic instance, that is, a non-backtracking call of the predicate.

1. External Predicates.

An external predicate is a parameterless Ada function that returns Boolean.

1.1. Specifying External Predicates.

The extended predicates must be described for the Logic Server in the form of the predicate table, and that predicate table must be submitted to the Logic Server as the corresponding argument of either the Open function (for a Prolog session, see section 3.1 below) or an instantiation of the Prolog.Library generic package (for a Logical Extension Library, see section 4.1 below).

The predicate table is an array of predicate descriptors. Each descriptor is a row (record) in the predicate table. That row describes the predicate usage for the Logic Server (tells it the predicate's functor and arity) and for the TAP bindings themselves (specifies the predicate's kind), and provides an access to the function that actually computes the predicate. The predicate table is supposed to be declared with the following way:

PTable : constant Predicate_Table := (
                                      1st predicate-descriptor,
                                      2nd predicate-descriptor,
                                      ...
                                      );
A predicate-descriptor is a record with the following four fields:

predicate's functor (name)
a string, prefixed by "+", where "+" renames To_Unbounded_String function;
the name of the predicate as it appears in a Prolog program
predicate's arity
non-negative integer (probably a constant);
the number of the predicate's arguments
predicate's kind
one of three:
Pure_Predicate
does not participate in backtracking;
Generator_Shared
participates in backtracking, but cannot differentiate its instances;
Generator_Reenterable
participates in backtracking, and can differentiate its instances
access to the predicate's executor
access-to-parameterless-Boolean-function

Here is an example of the predicate-descriptor:

  (+"hit", 2, Pure_Predicate, Find_And_Hit'Access)

1.2. Inside External Predicate.

All the subroutines described in this section may be called only within a predicate execution context. The Call_Context_Error exception is raised (with the EM_Int_1 message) when this rule is violated.

There are 3 subroutines that deal with the predicate's parameters inside the predicate: Get_Parameter_Mode, Get_Parameter and Set_Parameter. The first argument for these functions is always the index of the predicate's parameter to deal with. The Call_Argument_Error exception (with the EM_Index message) is raised when that argument appears greater than actual number of the predicate's parameters.

The function Get_Parameter_Mode tells whether the investigated parameter is in input mode (that is, contains a value) or it is in output mode (that is, can accept a value). In the former case this function returns the Mode_In constant, in the latter case it returns the Mode_Out constant. Here is the declaration of this function:

  function  Get_Parameter_Mode(
                               Index : in Positive
                              )
    return Parameter_Mode;
The function Get_Parameter returns current string value of the parameter. It raises the Call_Access_Error exception when applied to a parameter in output mode (with the EM_Get message in this case) or when it can't retrieve the parameter's value for another reason (with the EM_Ret message in this case). Here is the declaration of this function:

  
  function  Get_Parameter(
                          Index : in Positive
                         )
    return VString;
Note, that in a case of a complex term (that is, not a string, atom or number) in a parameter, there is a limitation for the resulting string length, though not too restrictive (see Term_String_Length_Limit constant in the private part of the Prolog package spec; the value of this constant corresponds to the default value of the readbuffer configuration parameter for the Amzi Logic Server).

The procedure Set_Parameter sets new value for the paraneter. That new value is passed to the procedure as its second argument. The third argument of the procedure tells the Logic Server how to interpret the string value -- either as an Prolog atom or as a string literal. For the former interpretation Meaning => Name should be used, and Meaning => Literal -- for the latter. The Call_Access_Error exception (with the EM_Set message) is raised at an attempt to set a value to non-variable parameter (that is, to a parameter in input mode). Here are two declarations of this procedure (one for String argument, and another -- for VString, that is, Unbounded_String argument):

  procedure Set_Parameter(
                          Index   : in Positive;
                          Value   : in String;
                          Meaning : in Prolog_String
                         );

  procedure Set_Parameter(
                          Index   : in Positive;
                          Value   : in VString;
                          Meaning : in Prolog_String
                         );
The procedure Get_Predicate_Profile extracts the functor (name), arity (number of arguments) and the kind of the predicate itself. Here is the declaration of this procedure:

    
  procedure  Get_Predicate_Profile(
                                   Functor : out VString;
                                   Arity   : out Natural;
                                   Kind    : out Predicate_Kind
                                  );
The function Get_Call_Purpose tells whether current call of the predicate is the first call of this predicate instance or not. In the former case it returns the Call_Start constant, and in the latter case (which includes all backtracking calls) it returns Call_Continue constant. Here is the declaration of this function:

  function Get_Call_Purpose return Call_Purpose;
The function Get_Instance_Id returns the number of this predicate instance (among all instances of the current predicate). It is useful for the predicates of the Generator_Reenterable kind only, because for other tho kinds of the predicates this function always returns 1. Here is the declaration of this function:

  function Get_Instance_Id return Positive;

2. Application Configurations.

Basically, there are two kinds of relationship between an Ada program and the Logic Server:

Additionally, an Ada program that creates a Prolog session may also implement the external predicates. In other words, the Logic Server may use the external predicates from the program that creates the session, as well as from the dedicated Logical Extension Libraries.

So, there are two basic configurations of an application that combines an Ada program with a Prolog program using the Logic Server. In the first of these configurations, the Ada program is the main program of the application; it creates a Prolog session, telling the Logic Server three things:

In the second basic configuration, the main program of the application is a Prolog program (actually, the Logic Server, which runs that Prolog program). The Logic Server loads the Logical Extension Libraries for that Prolog program, using the names/locations supplied in the separate configuration file. When the Logic Server loads a Logical Extension Library, it issues the initialization call to the library. In response to that call, the library supplies the description of its external predicates -- the predicate table. At the second initialization call from the Logic Server, the library possibly asserts some additional rules (that is, adds them to the Prolog database), which facilitate the use of the supplied external predicates.

An Ada program may create several Prolog sessions (with different Prolog programs), and use them simultaneously.

3. Prolog Session in Ada Program.

3.1. Initialization and Termination.

An Ada program that queries a Prolog program (or manipulates a Prolog database) must explicitly open a Prolog session before those actions. When the program completes its interactions with the Prolog program it should close the Prolog session.

The Open function takes from two to four arguments: the first argument is a string - the filename of a Prolog program to be loaded (if that filename does not include a suffix then ".xpl" is appended to it as the default suffix); the second argument is access to a predicate table (or No_Extended_Predicates constant, if the program does not supply extended predicates). Optional third argument is an Unbounded_String that presents the names (separated by commas) of Logical Library Extensions to be loaded together with the Prolog program. Optional fourth argument is an Unbounded_String that presents the configuration parameters for the Logic Server (see Amzi Logic server documentation for them). The function returns a value of the private type Session. That value identifies the session, and must be passed as the first argument within all other calls of the Logic Server except of the functions dedicated to the use from inside the external predicates. Here is the declaration of the Open function:

                                 
  function  Open(
                 Program_Name : in String;
                 Predicates   : in Predicate_Table;
                 Libraries    : in VString := Nul;
                 Configuration_Parameters: in VString := Nul
                )
              return Session;

The Close procedure has single argument - the identification of the Prolog session. The procedure invalidates that session identification. Here is the declaration of the Close procedure:

  procedure Close(Process: in out Session);  -- the argument becomes null as a result

3.2. Querying Prolog Program.

Once a Prolog session is established, the Ada program may issue queries to the Prolog program. For that the Apply function should be called, and a Prolog "goal" string must be passed as the second argument within the invocation. The first argument of the call must be the session identification (that was received as the result of the Open function), and the third argument is a Boolean permission for subsequent retries (provided that the original Apply succeeded).

The Apply function returns True if the "goal" succeeded in the Prolog program, otherwise it returns False.

The "goal" string may contain Prolog variables. When the Apply succeeds, those variables obtain their values (become bound in the Prolog terminology), and those values may be retrieved for inspection using the Display_All and Display_Variable functions (see Section 3.3 below).

Here is the declaration of the Apply function:

  function  Apply(                                   -- Prolog.Synonyms.Query
                  Process    : in Session;           -- session identification
                  Cause      : in String;            -- Prolog "goal"
                  Multi_Step : in Boolean := true    -- permission for Proceed
                 )
              return Boolean;
As already mentioned in this section, the "goal" may be retried, provided that previous call of the Apply function succeeded and the Multi_Step argument for that call was True. The Proceed function exists for that purpose.

The Proceed function accepts single argument -- the session identification. It returns True or False, like the Apply function. When it succeeds (that is, returns True), new values are assigned ("bound") to the Prolog variables within the "goal". The Proceed function may be called repeatedly while it succeeds. Here is the declaration of the Proceed function:

                  
  function  Proceed(Process: in Session)             -- Prolog.Synonyms.Retry
               return Boolean;

3.3. Inspecting Query Results.

Besides of using the basic success/failure that is returned by the Apply and Proceed functions, a caller may also inspect the detailed results by retrieving the string values of the variables embedded into the "goal" string. Two functions are provided for this purpose: Display_Variable and Display_All. Also, the third function, Count_Variables is provided for convenience. All those three functions may be called only after Apply, provided that the recent Apply or Proceed returned True. If the latter condition is violated then the Call_Context_Error exception (with the EM_Disp message) occurs. All those functions take the session identification as the first argument.

The Display_Variable function takes a variable's name as its second argument, and returns the string value of the variable. If there is no variable with the name supplied then the Call_Argument_Error exception (with the EM_Var message) occurs. Here is the declaration of the Display_Variable function:

  function  Display_Variable(
                             Process : in Session;
                             Name    : in String
                            )
              return VString;  -- value of variable
The Display_All function returns the string that contains a sequence of the "name=value" pairs for all variables that participate in the "goal" string. Using the second argument, the caller may provide own separator string, which will be inserted between those pairs. Here is the declaration of the Display_All function:

  function  Display_All(
                        Process   : in Session;
                        Separator : in String := ", "
                       )
              return VString;  -- consists of the name=value pairs with the
                               -- Separator between them 
The Count_Variables function returns the number of Prolog variables in the "goal" string. Here is the declaration of the Count_Variables function:

  function  Count_Variables(Process : in Session)
              return Integer;

3.4. Manipulating Prolog Database.

The Prolog clauses (that is, the facts, and the rules) may be added to the dynamic database of the Prolog program, or deleted from it. There are three subroutines that facilitate these actions: Prepend, Append, and Remove. All them take the session identification as the first argument, and the Prolog clause to be added or deleted - as the second argument.

The manipulations on the Prolog dynamic database generally require a closer look at the Prolog side of the session than it is necessary for other interactions with the Prolog program within a session. For the simple cases the proceedings are quite straightforward, but there may be problems with the more complicated ones. First, if the Prolog program uses modules then some preliminary measures are needed (in the Prolog program) that facilitate an addition of substantially new clauses (see Amzi Logic Server documentation for that). Second, a deletion uses Prolog's unification algorithm (and not the exact comparison of the string representation of the terms) during the search, so it cannot be guaranteed without a care at the Prolog side that the deleted clause will be intended one.

The procedure Prepend inserts a Prolog clause at the beginning of the Prolog database. Here is the declaration of the Prepend procedure:

  procedure Prepend(                               -- Prolog.Synonyms.Assert_A
                    Target : in Session;
                    Clause : in String
                   );
The procedure Append inserts a Prolog clause at the end of the Prolog database. Here is the declaration of the Append procedure:

  procedure Append(                                -- Prolog.Synonyms.Assert_Z
                   Target : in Session;
                   Clause : in String
                  );
The function Remove deletes a Prolog clause from the Prolog dynamic database. It returns True if a clause was successfully deleted, and False otherwise (that is, if it did not find an approriate clause to delete). Note, that the second argument - the clause presented for deletion - is considered not as exact search key, but as the pattern for the Prolog's unification; thus a careless deletion may produce unpleasant surprises if the Prolog database contains several fitting candidates. Here is the declaration of the Remove function.

                  
  function  Remove(                                -- Prolog.Synonyms.Retract
                   Target : in Session;
                   Clause : in String
                  )
              return Boolean;

3.5. Linking Logic Server to Ada Program (for MS Windows).

In Windows, the Logic Server is implemented as a DLL, namely amzi.dll . Therefore, that DLL must be on the search path at run time, and an Ada program that creates a Prolog session must be linked with an appropriate import library.

The import library libamzi.a, which is supplied with the TAP distribution, should be used for linking, instead of the standard amzi.lib from the Amzi Logic Server installation. The latter cannot be used for that purpose due to uncommon combination of the call convention and the entry point names in amzi.dll (which are reflected in amzi.lib, and are incompatible with the code geneterated by the GNAT compiler). So, the libamzi.a from the TAP distribution, and not the amzi.lib from the Amzi Logic Server installation, must be present on the linker's search path.

In anticipation of possible new releases of Amzi Logic Server, and consequently, new versions of amzi.dll, the method for creation of libamzi.a (from amzi.dll) is provided here. Given the amzi.dll and the file amzi.def (the latter is included in the TAP distribution), the following command produces libamzi.a :

  gnatdll -d amzi.dll -e amzi.def -k 
(gnatdll is a tool from the GNAT installation; note the switch -k, it is crucial in that special case).

4. Logical Extension Library in Ada.

4.1. Source Code Considerations.

The source code for a Logical Extension Library must contain entities of three kinds:

  1. parameterless Boolean functions, which are the executors for external predicates encapsulated in this library;
  2. predicate table, which describes those predicates
  3. two exported functions: InitPreds and LSAPI_Ready, which will be called by the Logic Server within the process of the Logical Extension Library initialization. The InitPreds function is required by the Amzi Logic Server (see the docs in its installation), while LSAPI_Ready function is required by the TAP bindings for the proper initialization of the predicates of the Generator_Shared and Generator_Reenterable kind.

The rules for the predicate table in a Logic Extension Library are the same as those for the predicate table already described for use with a Prolog session (see Section 1.1 above).

The exported functions InitPreds and LSAPI_Ready are standard with the TAP bindings, and should not be changed unless there is a special need for that. The only problem with them is that the InitPreds function body needs the predicate table, which certainly can't be universal. Therefore, those two functions are placed together into generic child package Prolog.Library, and the predicate table is made its single parameter.

So, for providing the required exported functions, it is sufficient (and recommended), to instantiate (in the Logical Extension Library package spec, after the predicate table) that Prolog.Library package with the name of the predicate table as its parameter (the package name used for the instantiation does not matter).

4.2. Linking Logical Extension Library DLL (for MS Windows).

Assuming that the source code of a Logical Extension Library is compiled, and the result of that compilation is the pair of files MyLSX.o, MyLSX.ali, the following command builds the MyLSX.dll:

  gnatdll -d MyLSX.dll -e AdaLSX.def MyLSX.ali -n
where the switch -n prevents creation of the import library libMyLSX.a (that import library would be of no use, so there is no reason to build it). The AdaLSX.def file is provided in the TAP distribution, and has the following contents:

EXPORTS
    InitPreds=InitPreds@8
    LSAPI_Ready=LSAPI_Ready@8

5. Exceptions and Error Handling.

Various checks are performed, both inside the Logic Server and inside the TAP bindings, in order to ensure correctness of the requests and their arguments. When a violation of the required conditions are encountered, an appropriate exception is raised. The following exceptions are provided for that purpose in the Prolog package:

Logic_Server_Exception -- the Logic Server signals an exceptional condition
String_Overflow -- the size of a string argument is greater than established maximum
Call_Argument_Error -- improper argument value
Call_Access_Error -- attempt to retrieve Mode_Out parameter or to set Mode_In parameter
Call_Context_Error -- a subroutine is called out of proper context, e.g., Proceed without prior Apply or Get_Parameter from outside of a predicate function etc.
Call_Environment_Error -- a subroutine is called from inside an incompatible environment, that is, from a main program instead of a predicate library or vice versa.

Each time when one of those exceptions is raised, an appropriate message is associated with it. All those messages are declared individually, as the constant strings, in the Prolog package. Therefore the identifiers of those messages may be used as a tightening of an exceptional case, when an advanced diagnostic is needed inside a program. All those exception message identifiers have the prefix EM_. See the Prolog package spec for the full list of those messages.

6. Advanced and Additional Features.

6.1. Working with Prolog Lists as Predicate's Parameters.

The child package Prolog.Lists provides the facilities for dealing with Prolog lists in the parameters of an external predicate. These facilities rely upon the VString_Array type as the Ada representative for Prolog lists. Here is the declaration of this type:

  type VString_Array is array(Integer range <>) of VString;
A homogeneous linear Prolog list (that is, a list without sub-lists, and in which either all elements are names/atoms or all them are (back)quoted literals) may be directly translated to a parameter of an external predicate from a VString_Array vector using the additional form of the Set_Parameter procedure, provided within this package. This procedure accepts an array of VString_Array type, converts it to a Prolog list, and sets the predicate's parameter accordingly. Here is the declaration of this procedure:

  procedure Set_Parameter(
                          Index   : in Positive;
                          Value   : in VString_Array;
                          Meaning : in Prolog_String
                         );
Note, though, that you cannot use this procedure for heterogeneous or non-linear lists (for example, you cannot intermix in the list names with literals, and you cannot directly build trees with a single call of this procedure).

Alternatively, you may convert a VString_Array vector to the string representaion of the corresponding Prolog list with the function Format_Linear_List (provided within this package), and then assign the resulting string to the predicate's parameter using the basic form of the Set_Parameter procedure. The Format_Linear_List function takes a VString_Array vector, converts it to the string representation of the corresponding Prolog list, (perhaps quoting the elements of the list, according to the second argument), and returns that string. Here is the declaration of this function:

  function Format_Linear_List(
                              Value   : in VString_Array;
                              Meaning : in Prolog_String
                              )
    return VString;
Note, that it is possible to create non-linear (and to some degree, heterogeneous) lists using multiple calls of this Format_Linear_List function, that is, submitting the result of a previous call to the subsequent call (as an element of the vector argument).

For dealing with input parameters of external predicates, four subroutines are provided within this package: Is_List function, Count_Linear_List function, and two forms of Extract_Linear_List subroutine -- procedural and functional. All those subroutines take the string argument, and try to treat it as the string representation of a Prolog list (which may be non-linear, but the subroutines do not dive into the deeper levels, except for the syntax check). So, for dealing with a list, which is the contents of an input parameter of an external predicate, you should first take that contents as a string (VString) using the Get_Parameter function, and then apply these subroutines to that string.

The function Is_List tells whether its argument is a correct string representation of a Prolog list (perhaps non-linear) -- it returns True in the case, and False otherwise. Check a string with this function before submitting it to the Count_Linear_List and Extract_Linear_List subroutines, to be safe from the possible List_Format_Error exception (unless the proper list syntax is guaranteed by other means). Also, this function facilitates the multi-step extraction of a non-linear list, which is mentioned below, at the end of this section. Here is the declaration of this function:

  function Is_List(
                   Value : in VString
                  )
    return Boolean;
The function Count_Linear_List assumes that its argument is a string representation of a Prolog list, and returns the number of elements in that list (counting the upper level only). This number may be zero, indicating an empty list. Here is the declaration of this function:

  function Count_Linear_List(
                             Value : in VString
                            )
    return Natural;
The function Extract_Linear_List assumes that its argument is a string representation of a Prolog list, and returns its contents as an array of VString_Array type (considering the upper level only, that is, returning a sub-list of the list as its full string representation, assigning it to a single element of the output vector). Here is the declaration of this function:

  function Extract_Linear_List(
                               Value : in VString
                              )
    return VString_Array;
The procedure Extract_Linear_List is a procedural form of the Extract_Linear_List function described above. The only difference from the latter is the way of returning the resulting vector:: in this procedure the vector is returned via the second, output parameter. Here is the declaration of this procedure:

  procedure Extract_Linear_List(
                                Value : in VString;
                                Result : out VString_Array
                               );                             
Note, that using the Extract_Linear_List function or procedure you can extract the contents of non-linear lists (e.g. trees) -- calling it separately for each sub-list extracted (as a string -- an element of the array) by the previous call.

6.2. General Unification: Unifying Predicate's Parameters And Obtaning Unified Result Of Query.

The procedure Unify_Parameter is for use inside an external predicate. It provides an opportunity to imitate an IN OUT mode for a predicate's parameter. Also, it exploits the general unification algorithm (instead of mere copying) for setting the parameter's value. The first argument of the procedure is the parameter's index (exactly as with other subroutines that deal with the predicate parameters) and the second argument is a string representation of a term to be unified with the current value of the parameter. Here is the declaration of the Unify_Parameter procedure:

  procedure Unify_Parameter(
                            Index : in Positive;
                            Value : in String
                           );
A whole result of a Prolog query as an unified term may be retrieved using the function Get_Unified, which should be called for that (if needed) immediately after a successful Apply or Proceed call. The function Get_Unified takes the session identification as its single argument, and returns the string representation of the unified term. Here is the declaration of the Get_Unified function:

  function Get_Unified(Process: Session) return VString;
Both Unify_Parameter and Get_Unified subroutines are provided within the child package Prolog.Unification .

6.3. Synonyms for Functions That Have Prolog Equivalents.

The package Prolog.Synonyms contains the renamings for several functions from the Prolog package. Those renamings provide the names that are customary to the experienced Prolog programmers. Here is the table of those synonyms:

synonymrenames
Query Apply
Retry Proceed
Assert_A Prepend
Assert_Z Append
Retract Remove

To use those synonyms in your own Ada package, make sure that your package has the following context clause:

  with Prolog.Synonyms;
and perhaps, the corresponding "use" clause:

  use Prolog.Synonyms;
Note, that the names of parameters of Query and Retry functions are different from those used in Apply and Proceed:

Query/RetryApply/Proceed
Database Process
Goal Cause
Retry_Permitted Multi_Step

7. Limitations.

7.1. No Multi-Tasking Supported.

The TAP bindings do not support multi-tasking, i.e. it is assumed that all calls for the subroutines of this package are made from the same task. Actually it means that user (programmer) is fully responsible for proper serialization of the requests to the Logic Server.

7.2. Reserved Predicate Names.

Several predicate names are reserved by this package for its own use and therefore must not be used as the names of predicates, neither external nor internal to a Prolog program. All those name begin with the prefix ext_. Here is the whole list of those reserved names:

ext_display
ext_gate
ext_exec_*  (where * is the name (functor) of some external predicate)
ext_cont_*  (where * is the name (functor) of some external predicate).

7.3. No Support for Prolog Modules.

The TAP bindings do not make any precautions for the Prolog's modules. That feature is simply ignored by the bindings. So, if the Prolog modules are used, it is programmer's responsibility to deal with all needed adjustments and/or safeguards.