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.
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.
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(); |
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.
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.
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(...'
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).
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:
X foo(a,b) hello $this is a string$ 'a long atom name' [a,list,of,stuff,34,$foo$,foo(a,b),Z,Y] 42 3.7 13 + 8.9 / 16 even_rules(X) :- are(Y,Z), terms
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.
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.
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.
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
?- sister(mary,X).
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).
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.
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.
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 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.
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) |
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(); ... |
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.
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.
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().
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() |
The two functions, CallStr() and ExecStr() both have counterparts that issue Prolog queries with just terms as input.
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.
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.
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.
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.
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:
The siblings of Mary are: mary kathy ricky jenny
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().
In addition to the string-based functions, the API provides specific functions to make simple terms. These are:
Examples:
declare TERM t declare POINTER p ... t = ls.MakeAtom("hello") t = ls.MakeStr("hello") t = ls.MakeInt(47) t = ls.MakeFloat(4.7) ... |
Because Prolog isn't typed, you sometimes need to determine the type of term returned to your host language program. GetTermType() performs that function.
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.)
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:
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) |
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.
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().
These functions can be used in conjunction with the string-based functions. For example, these two function calls are equivalent:
term = MakeFA("sister", 2) term = StrToTerm("sister(X,Y)")
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) |
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
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
Made list [third,second,first] Popped third Popped second Popped first
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.
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.
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:
ls.ExecStr(&term, "consult(ducks)") ls.ExecStr(&term, "reconsult(ducks)") ls.ExecStr(&term "load(ducks)")
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
ls.Init("") ls.Load("amzi") ls.ExecStr(&term, "consult(ducks)")
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().
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:
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.
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.
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.
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
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
element 3 is 9 element 4 is 16
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.
The user implementation of InitPreds() will call either AddPred or InitPreds to initialize the extended predicates.
LSXs can either be loaded using a configuration file parameter, or using a Prolog directive or goal.
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.
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.
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.
lsxload = xarray
Other applications can either load LSX files from the .cfg file or explicitly load the desired files. To load from the .cfg file, use
lsInitLSX()
To load explicitly, use
ls.AddLSX("xarray")
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
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:
int my_getc(FILE*); int my_ungetc(int, FILE*); int my_puts(char*); int my_putc(int);
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.)
These API functions let you inform the Logic Server of the functions to use for stream 3 I/O.
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
The following statements set all of the I/O streams to use the functions defined for stream 3.
lsSetStream(CUR_OUT, 3) lsSetStream(CUR_IN, 3) lsSetStream(CUR_ERR, 3) lsSetStream(USER_OUT, 3) lsSetStream(USER_IN, 3) lsSetStream(USER_ERR, 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.)
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; } |
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.
'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(Error, AttributeList)
Error is either:
syntax_error system_error
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.
?- catch(read(X), E, writeq(E)). % create a read error x badop y. error(syntax_error, [type = read, rc = 407, message = $Unexpected operator$, predicate = $read_/2$, callstack = $- catch/3;+ catch/3;--- top of stack ---$, read_buffer = $x badop **NEAR HERE** y$, read_file = $$, line_number = 0])
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. |
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.
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:
try { // Logic Server stuff } catch(LSException &e) { // recovery stuff }
Class LSException has a number of member functions that let you get details about the exception. See the LSAPI Reference for the LSException methods.
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.
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.
There are a few functions that pass additional information back and forth between Prolog and C. These are
Copyright ©1987-2011 Amzi! inc. All Rights Reserved. Amzi! is a registered trademark and Logic Server is a trademark of Amzi! inc.