LPA Intelligence Server

The WIN-PROLOG Intelligence Server provides full-featured Prolog server facilities for applications written in pretty much any Windows programming language or visual development system. Using just six calls, applications can set up and control an arbitrary number of nested Prolog queries. Two calls are used to start up and close down the server:

	LoadProlog( string, buffersize, encoding )
	HaltProlog( ID )

The first call loads WIN-PROLOG in a background "server" mode, without displaying any windows, icons, or other features. The <string> parameter can be used to pass command line switches, such as for setting heap or program space allocations, to WIN-PROLOG. The buffersize argument specifies the size of the intermediate data area for passing data to and from Prolog. The encoding argument can be either 0 or 1 and indicates the desired text encoding, 0 is 8-bit ISO/IEC-8859-1 text, and 1 is 16-bit unicode text. From C, the call:

    int ID;

	if	( (ID = LoadProlog("/h64 /p512", 0x10000, 0)) > 0 )
		... carry on happily
		... display an error and quit
	} ;

attempts to load WIN-PROLOG, passing it the /H64 (heap space = 64kb) and /P512 (program space = 512kb) switches, the buffer size of 0x10000, and 8-bit ISO/IEC-8859-1 text encoding, and if successful, runs the program returning an ID for that instance of Prolog. You could also open a second copy of Prolog in the same way, remembering to save the returned ID into a separate variable! If Prolog could not be loaded, an error message is displayed and the application quits.

Once loaded, the application and initialise, call and terminate any number of queries, and when finished with Prolog, can free up the memory that was used with the call:

	HaltProlog(ID) ;

This tells WIN-PROLOG to shut down the server interface and halt execution.

While running Prolog in "server" mode, there are three main functions which are used to process queries:

	InitGoal( ID, string )
	CallGoal( ID )
	ExitGoal( ID )

The first is used to initialise a goal, which is passed to WIN-PROLOG simply as text. From C, the call:

	InitGoal( ID, "member(2,[1,2,3]). " ) ;

sets up the goal, "member(2,[1,2,3])", on the WIN-PROLOG "server" stack for the instance referenced by ID, but does not yet call it. When ready to run a goal which has been set up, the application makes the call:

	Result = CallGoal(ID) ;

The "Result" variable should have been declared as a far string pointer, and upon completion, points to a string which indicates the result of the call, followed by any user output created by the goal. In the above case, the string:

	"T 0000"

will be returned, indicating the result "true" (T) for query number "0" (0000). The query number indicates how many nested queries are currently on the stack. If the call:

	Result = CallGoal(ID) ;

is made again, the result will this time be:

	"F 0000"

indicating that on backtracking, there were no more solutions (F is for "fail"). The application can then clear this query from the stack using the call:

	ExitGoal(ID) ;

Goals initialised with the "InitGoal" function can be called as often as required by "CallGoal", with each such call finding the next available solution. When the call finally fails (the return string begins with F), the goal should be cleared with "ExitGoal". This latter function can be invoked earlier, if desired: there is no need to wait until failure.

While a goal is on the stack (after a call to "InitGoal", but before a corresponding call to "ExitGoal"), a second call to "InitGoal" can initialise a "nested" query. This query will return the next available 4-digit number with its solutions, to help identify its output from that of the previous query. Once this goal has been called with one or more calls to "CallGoal", it can be removed from the stack with a call to "ExitGoal". At this point, the previous query can be resumed from where it left off. For example, the calls:

	InitGoal(ID, "member(X,[one,two,three]), write(X). ") ;
	Result = CallGoal(ID) ;

returns the "Result" variable pointing to a string containing the output:

	T 0000

Another call:

	Result = CallGoal(ID) ;

will return the output:

	T 0000

If at this point, the calls:

	InitGoal(ID,"member(X,[a,b,c]), write(X). ") ;
	Result = CallGoal(ID) ;

are made, the output will be:

	T 0001

The number, "0001", indicates that this is a nested query, with one "pending" query. Another call:

	Result = CallGoal(ID) ;

will return the output:

	T 0001

At this point, the second (nested) query can be terminated with the call:

	ExitGoal(ID) ;

Now, if the call:

	Result = CallGoal(ID) ;

is made, the original query will be resumed, producing the output:

	T 0000

Any number of queries can be nested, subject only to the space specified in WIN-PROLOG's stack and heap settings.

Two more types of output are returned from queries: firstly, any errors which occur in the Prolog query, and which are not caught or handled by user code, result in the query being aborted with an error message. For example, the C calls:

	InitGoal( ID, "bad syntax here. ") ;
	Result = CallGoal(ID) ;

will return a string containing:

	E 0000
	* Syntax Error
	! Error 42 - ... etc

The "error" condition is indicated by the letter E, while the query number that contained the error is also given, followed by the text of the error message. All previously nested queries will be aborted if an error like this occurs, so the application will need to restart any such queries after handing the error.

User code can use the catch/2 predicate, or the '?ERROR?'/2 hook, to handle errors in a controlled way from within WIN-PROLOG itself, so that errors like that just described need never occur.

The final response from "CallGoal" is a special case, and occurs only when the WIN-PROLOG code calls a special server predicate, input/2. This predicate is used to elicit user input or other responses from the "client" application. If a Prolog program is written as follows:

	foo :-
	   input( `Please give me some data`, DATA ),
	   write( `Thanks - you supplied: ` ),
	   writeq( DATA ).

and is then called with the C calls:

	InitGoal( ID, "foo. " ) ;
	Result = CallGoal() ;

the string returned will contain:

	I 0000
	Please give me some data

This is an "input" request, indicated by the letter I. There is only one valid response to this, which is to call the function:

	TellGoal( ID, string )

This function returns data contained in its string to WIN-PROLOG, and resumes the goal that requested the data. It returns any output from the goal in exactly the same way as "CallGoal": this output may itself be a further request for input, in which case "TellGoal" must be called again. The required input can be obtained in any way, either by prompting the user, or looking up values in a relational database or whatever. In the above case, the C call:

	Result = TellGoal( ID, "Here is your data!" ) ;

will return the output:

	T 0000
 	Thanks - you supplied: `Here is your data!`

The beauty of the Intelligence Server is that applications can be written in C, C++, Visual Basic, Delphi, and pretty much any other Windows language, which can use the phenomenal inferencing power of Prolog completely in the background, while allowing the Prolog code to request the input needed to complete its computations. Any number of incomplete queries can be nested, limited only by available memory settings, which are configurable by the client application.

All this power is available through a simple text interface, using just six simple functions: there is no need to learn about WIN-PROLOG's internal data structures, or how to set up goals on its query stack. The client application simply sends the text of queries to Prolog, and (apart from the 8-byte flag and query number header) receives back only that output performed by the user's Prolog code, hugely simplifying the task of interpreting Prolog data back in the client application.