Alexander Kopilovitch
aek@acm.org, aek@vib.usr.pu.ru
This tutorial presents a complete example of the TAP (Thick Ada-Prolog) bindings basic usage, with the detailed explanations at each step. Note, though, that only basic features are explored in this example, and if you want more then you should get across the TAP Manual.
A reader is assumed familiar with the Ada basics as well as with the Prolog basics. Also, an acquaintance with Amzi Logic Server (www.amzi.com) -- at least with the Overview in its "Logic Server User's Guide & Reference" is desirable, although perhaps not strictly necessary for the first reading. Throughout this tutorial the language name Ada means Ada 95.
This is a Windows-oriented version of the Tutorial. For Unix/Linux environment all command files and LSX building technique is different from those shown here.
There are two old gentlemen dining every day in a club. These gentlemen are very similar to each other in their tastes and habits. So it is quite natural that they always have their dinner together, at the same table for two persons. The notable thing about them is their strong tradition to read magazines during eating, and change the magazines with the next dish. A waiter puts a pile of different magazines before them along with the first dish, and the diners take them one by one, always picking the topmost one, and never returning a used magazine to the pile, but simply dropping it on the floor. The combination of a particular dish with a particular magazine either pleases or annoys the diner. Our problem is to find (and display) all the courses within a dinner for which the participant moods are different. |
Additionally we assume for our dealings with the problem, that a diner is pleased if the first word of the current dish's name has a common letter with the current magazine's title, and is annoyed otherwise.
The basic entities are represented by three external predicates:
dish/1
, magazine/1
, and mood/3
.
The predicate dish/1
is called for a course
change; it succeeds until the end of meal is reached, then it fails. On success,
it bounds its argument (which must be a variable) to the next
dish name. The predicate magazine/1
similarly bounds its argument
to the name of the next topmost magazine from the pile.
The predicate mood/3
accepts a dish name in its first argument, a magazine name
in its second argument, computes the appropriate mood name, and bounds the latter
to its third argument.
The second and third stages -- an introduction of high-level entities abd formulating of the question go differently in Ada and Prolog and in their various mixtures. In our problem this is mostly because of rather different facilities for looping in these languages. In a Prolog program (or the Prolog part of the program) the high-level entities will basically look similar to the following:
course(D,M,X) :- dish(D), magazine(M1), mood(D,M1,X1), magazine(M2), mood(D,M2,X2).The following rules facilitate the displaying of the results (the backquote sign inside them is the delimiter for a literal string in Amzi Prolog)
display_course(X1, X2, D, M1, M2) :- display_dish(D), display_context(X1, X2, M1, M2), nl. display_dish(D) :- write(` dish=`), write(D), nl. display_context(X1, X2, M1, M2) :- write(` magazine_1=`), write(M1), write(` mood_1=`), write(X1), nl, write(` magazine_2=`), write(M2), write(` mood_2=`), write(X2), nl.
The question to be answered in interactive mode (that is, in the Prolog Listener) would be formulated as the following Prolog query:
?- course(D,M1,X1,D,M2,X2), X1 \= X2, display_course(X1, X2, D, M1, M2), nl.(which should be retried until failed, for obtaining all cases of different moods, as required by the problem statement), but automatic looping, needed in the compiled mode, brings some decorations within and around that code.
You may look at the pure Prolog (p_dinner.pro) and pure Ada (a_dinner.adb) implementations of the task. You may even run those programs (go to the directory Samples, run command files p_build.bat and a_build.bat, and then run p_run.bat command file -- for Prolog or a_dinner.exe program -- for Ada).
For Ada, the specification of the basic entities is defined by the
following Ada package Dinner_Services
(unary operation "+
" is defined in the
Prolog
package, and renames To_Unbounded_String
function):
with Prolog; use Prolog; with Prolog.Library; package Dinner_Services is function Get_Dish return Boolean; function Get_Magazine return Boolean; function Determine_Mood return Boolean; PTable : constant Predicate_Table := ( (+"dish", 1, Pure_Predicate, Get_Dish'Access), (+"magazine", 1, Pure_Predicate, Get_Magazine'Access), (+"mood", 3, Pure_Predicate, Determine_Mood'Access) ); package LSX is new Prolog.Library(PTable); end Dinner_Services;Let us review the above package spec in detail. The spec consists of three kinds of items: first, the set of the executor function prototypes for the predicates; second, the predicate table -- the array of specifications of the predicates for the Logic Server; third, the instantiation of the generic package
Prolog.Library
, which immerses the initatialization subroutines, which are
needed when the external predicate stuff is encapsulated in a separate library
(that is, Windows DLL or Unix shared object).
An executor function prototype should always be in the form used above, that is,
a parameterless function returning Boolean. The function acquires the values of
predicate's arguments and assigns new values to them using the subroutines
Get_Parameter
and Set_Parameter
. Different predicates may share the same
executor -- so, the number of executor functions generally may be less than
the dimension of the predicate table.
Within the predicate table declaration, the first place in a row is for the predicate's
functor, that is, its name inside a Prolog program. The "+
" prefix
at the predicate's name provides the transformation to Unbounded_String
(the "+
" function is defined inside the Prolog
package).
The second place in a predicate table's row is for the predicate's arity, that is, the number of the predicate's arguments inside a Prolog program.
The third place in a predicate table's row is for the predicate's kind.
The Pure_Predicate
value here means that the predicate does not
participate in Prolog backracking (for other possible predicate kinds see
TAP Manual).
The fourth (last) place in a predicate table's row is for the predicate's executor. An access to a parameterless Boolean Ada function must be provided here.
An instantiation of the Prolog.Library
generic package is required only if there
is an intention to encapsulate the external predicates into a Logic Server
Extension, that is into a Windows DLL or Unix shared object. But it does no
harm to do so anyway, even if the package Dinner_Services
will be used within the main
program. Note that the name of the predicate table is the necessary argument
to that instantiation.
There is two-staged choice for the form of an application as it relates to the Logic Server. For one of them we must decide whether the main program of the application will be a Prolog program, which in this case contains a query in the form of a Prolog goal, or the main program will be Ada program, which queries the Prolog program using the Logic Server API. If the second mode is chosen then we must decide for another option: whether the external predicates will be encapsulated into a Logic Server Exetension, that is, a Windows DLL or Unix shared object, or they will be part of the main Ada program. So, we can create 3 different configurations:
In two latter cases the Prolog program does not contain a goal, but is queried via the Logic Server API by the main Ada program. All three just described configurations will be developed here below.
Now let us consider the implementation of the external predicates (for all
configurations it is the same). First, we provide the package
Dinner_Supply
, which serves as a store, that is, provides a meal
and magazines for a dinner:
with Prolog; use Prolog; -- needed here for VString and "+" declarations only, package Dinner_Supply is Meal : array(Positive range <>) of VString := ( +"black pudding", +"crab soup", +"steak & kidney pie", +"lemon tart" ); Pile : array(Positive range <>) of VString := ( +"Quizionary", +"Nose To Nose", +"Myself", +"General Dissent", +"Veteran's Opinion", +"Corporate Science Weekly", +"Truth, Wealth, Health", +"Bribery Times" ); end Dinner_Supply;Then, using the above package we implement all needed predicates. Here is the code:
with Prolog; use Prolog; with Ada.Strings.Unbounded; use Ada.Strings.Unbounded; with Dinner_Supply; use Dinner_Supply; with Ada.Text_IO; use Ada.Text_IO; package body Dinner_Services is Run : Session; Course_Number : Natural := Meal'First - 1; Top : Natural := Pile'First - 1; function Get_Dish return Boolean is Current_Dish : VString; begin Course_Number := Course_Number + 1; if Course_Number <= Meal'Last then Current_Dish := Meal(Course_Number); Set_Parameter(1, Current_Dish, Name); return True; else return False; end if; end Get_Dish; function Get_Magazine return Boolean is Topmost_Magazine : VString; begin Top := Top + 1; if Top <= Pile'Last then Topmost_Magazine := Pile(Top); Set_Parameter(1, Topmost_Magazine, Name); return True; else return False; end if; end Get_Magazine; function Determine_Mood return Boolean is Dish, Magazine, Mood : VString; Dish_Space : Integer; Dish_Word : Unbounded_String; begin -- get input arguments Dish := Get_Parameter(1); Magazine := Get_Parameter(2); -- pick the first word in the dish name Dish_Space := Index(Source => Dish, Pattern => " "); if Dish_Space > 0 then Dish_Word := Head(Dish, Dish_Space - 1); else Dish_Word := Dish; end if; -- compute mood Mood := +"bad"; -- assume -- change to the "good" if the magazine's title and the picked part of -- the dish name have a common letter for I in 1 .. Length(Magazine) loop exit when Element(Magazine, I) = ' '; if Index(Source => Dish_Word, Pattern => Slice(Magazine, I, I)) > 0 then Mood := +"good"; end if; end loop; -- bound the third argument to the computed mood Set_Parameter(3, Mood, Name); -- always succeed return true; end Determine_Mood; end Dinner_Services;At this point we are able to compose the first of 3 possible configurations -- one with Prolog main program and the external predicates encapsulated in a Logic Server Extension. The Prolog program is a combination of the parts -- the rules and the goal (along with result display stuff):
course_body(D, M1, M2, X1, X2) :- dish(D), magazine(M1), magazine(M2), mood(D, M1, X1), mood(D, M2, X2). course(D, M1, M2, X1, X2) :- course_body(D, M1, M2, X1, X2), assert(consumed(D)), X1 \= X2. course(D, M1, M2, X1, X2) :- retract(consumed(U)), course(D, M1, M2, X1, X2). % ---------------------------------------------------------------------- main :- course(D, M1, M2, X1, X2), display_course(X1, X2, D, M1, M2), fail. display_course(X1, X2, D, M1, M2) :- display_dish(D), display_context(X1, X2, M1, M2), nl. display_dish(D) :- write(` dish=`), write(D), nl. display_context(X1, X2, M1, M2) :- write(` magazine_1=`), write(M1), write(` mood_1=`), write(X1), nl, write(` magazine_2=`), write(M2), write(` mood_2=`), write(X2), nl.This Prolog program must be compiled and built with the Amzi Prolog compiler. The result, which represents the application for a caller, is the file Dinner.xpl .
The Logic Server Extension in the form of Windows DLL Dinner.dll can be produced
from the package Dinner_Services
(that is, the pair Dinner_Services.ads and
Dinner_Services.adb) presented above, with the following commands:
gnatmake -c -aI..\Specs -aL..\Lib Dinner_Services gnatdll -d Dinner.dll -e ..\Bin\AdaLSX.def -I..\Lib -I..\Bin Dinner_Services.ali -nwhere the file AdaLSX.def is supplied with these bindings, and has the following contents:
EXPORTS InitPreds=InitPreds@8 LSAPI_Ready=LSAPI_Ready@8After that, the utility "starter" program arun.exe (which is a slightly extended version of the arun.exe program from the Amzi Prolog distribution) can run the whole application, with the command:
..\bin\arun Dinner.xplprovided that the text file Dinner.cfg is present in the current directory, and contains the line:
LSXLOAD <full path to the dinner.dll>(the Samples directory contains the command file runXPL.bat for the run, along with the Dinner.cfg configuration file, which provides an API trace also).
Now let's turn to the other two possible configurations of the application.
In both of them the main program is an Ada program, and that main program
queries the Prolog code using the Logic Server API. The only essential
difference between them is that in one of them the Dinner_Services
package (Dinner_Services.ads, Dinner_Services.adb)
is included into the main program, while in the other one that package participates
in the application in the form of DLL -- exactly the same way as for the above
case of the Prolog main program.
Both configurations are represented by the code below, where the (GNAT-specific)
preprocessor is used for their separation. There are two significant differences
in the code, and preprocessor actuates appropriate lines according to the Boolean
value of Predicates_Here
symbol: if Predicates_Here
is
True
then the first configuration is choosen, thus package
Dinner_Services
is referenced in "with
" clause, and the
predicate table is submitted to the Open
function; otherwise,
package Dinner_Services
is not referenced, and
No_Extended_Predicates
constant is submitted to the Open
function instead of the predicate table, along with the predicate library filename
Dinner.dll as a configuration parameter. There are also two minor differences
-- at the beginning and at the end of the subroutine -- the configured versions
are given slightly different names for convenience. Here is the code:
with Ada.Text_IO; use Ada.Text_IO; with Prolog; use Prolog; #if Predicates_Here with Dinner_Services; procedure Example1 #else procedure Example2 #end if; is Run : Session; Got_One : Boolean; begin Run := Prolog.Open ("Dinner.xpl", #if Predicates_Here Dinner_Services.PTable #else No_Extended_Predicates, +"Dinner.dll" #end if; ); Got_One := Apply (Run, "course(D,M1,M2,X1,X2).", Multi_Step => True); while Got_One loop New_Line (2); -- display all variables in the name=value form Put_Line (S(Display_All (Run))); New_Line; -- then present "formatted" display Put_Line (" dish=" & S(Display_Variable (Run, "D"))); Put_Line (" magazine_1=" & S(Display_Variable (Run, "M1")) & " mood_1=" & S(Display_Variable (Run, "X1"))); Put_Line (" magazine_2=" & S(Display_Variable (Run, "M2")) & " mood_2=" & S(Display_Variable (Run, "X2"))); Got_One := Proceed (Run); end loop; Close (Run); #if Predicates_Here end Example1; #else end Example2; #end if;These two configurations may be built using the sets of commands:
gnatprep -DPredicates_Here=True example.adb example1.adb gnatmake -aI..\Specs -aO..\Lib -aL..\Lib -L..\Bin example1and
gnatprep -DPredicates_Here=False example.adb example2.adb gnatmake -aI..\Specs -aO..\Lib -aL..\Lib -L..\Bin example2 gnatmake -c -aI..\Specs -aL..\Lib Dinner_Services gnatdll -d Dinner.dll -e ..\Bin\AdaLSX.def -I..\Lib -I..\Bin Dinner_Services.ali -nand then run as example1.exe and example2.exe (note, that the command file build.bat in the directory Samples builds all three configurations -- that is, the two just discussed, and that with Prolog main program, which was discussed before).