¹SBC-Warburg, UK
E-mail: ASehmi@iee.org.uk
²Amzi! inc.,
40 Samuel Prescott Drive, Stow, MA 01775, USA
E-mail: mary@amzi.com
WebLS (pronounced webbles) is a web-based system that uses an embedded Prolog component to deliver diagnostic technical support. It interfaces with web servers using the standard and Windows CGI (Common Gateway Interface) interface [CGI, WINCGI]. The Prolog logic-base is implemented with the Amzi! Logic Server, and some support functions are written in 'C'.
WebLS needs to be available on many different web servers and usable by many different web browsers. CGI is currently the only common standard across servers. The CGI interface executes a program each time an HTML form is submitted. Under Windows 95, the web server communicates with the CGI program by reading and writing files. Under Unix and Windows NT, it communicates using the stdin and stdout streams. Because of this overhead, the dialog between WebLS and the user needs to be designed to minimize the number of HTML forms generated. Also the patience of the user will be sorely tested by a long, tedious interaction.
Another requirement is that the state of the Prolog logic-base needs to be maintained across CGI invocations. And because multiple people will be using web-based tech support at the same time, the state for each of them must be separately maintained.
The most important requirement for WebLS is that the rules in the logic-base must be easy to maintain, as this is the only way the tech support database can be kept up-to-date. An out-of-date system will quickly fall out-of-favor with the users. And a too complex system will not be embraced by a wide range of webmasters.
Finally, the logic-base needs to take advantage of many existing resources already on the web, such as other documents, diagrams, databases, etc.
WebLS is being used to implement web-based technical support for Amzi! The following example is taken from that application.
First, an HTML form is used to gather general information about the problem. In Figure 1 the trouble is with Delphi under Windows 95.
This is followed by one or more additional questions to determine the exact problem. In Figure 2, WebLS only needs to know what version of Delphi is being used.
It is hoped that typically there will be between one and three additional sets of questions.
In Figure 3, knowing the user is working with Delphi 2.0 and the Amzi! Jan96 release is enough to determine the problem and the solution. It can be a couple of paragraphs, or it can be another web document. Here a paragraph describes how to fix the declarations to stdcall. Notice it also includes direct links for downloading the updated files.
WebLS uses a custom inference engine [Merritt, 96] instead of native Prolog in order:
The rule syntax takes advantage of Prolog's declarative nature so they are easy-to-read and maintain [Merritt, 94]. We use Prolog's ability to define new operators to implement the rule language, and to allow the logic-base to be compiled for faster execution.
The custom inference engine knows how to find an answer with known facts, ask for all the unknown facts together (instead of the customary one-at-a-time method used by most expert systems), batch related questions together (to further reduce the number of CGI invocations) and select hypotheses. Plus the engine implements a custom trace facility and error reporter so webmasters can debug knowledge bases using their web browser.
HTML forms are used to communicate with the user. For input, they are constructed on a field-by-field basis as needed by the inference engine. For output, they are generated based on the problem resolution and take advantage of HTML's syntax that allows links to other resources, such as other documents, a particular section of another document, executable code (applets or full programs), other files, lists of files, etc.
The logic-base is completely independent of the other two subsystems. In fact, it is stored as a separate source code file which is loaded by the inference engine. The CGI interface itself is a set of Prolog predicates for reading the CGI input from the web server and returning the HTML output. The inference engine is designed specifically for performing on-line tech support, and for efficient integration with the web server using the CGI interface. Lets look at each of the three subsystems individually.
fact(errorMessage, 'Local stack full'). fact(programVersion, '3.3 Mar96'). fact(environmentNameVer, 'Windows 95').
Facts are stored in the dynamic database instead of carried around as a list argument because:
The logic-base has the following general structure:
% Operator definitions :-op( . . . % System variables system( . . . % Fact attributes attr( . . . attr( . . . % Rules if . . . then . . . if . . . then . . . % Answers answer( . . . answer( . . .
The syntax for the rules is implemented by defining six Prolog operators as follows:
:- op(790, fx, if). % prefix operator :- op(780, xfx, then). % infix operator :- op(775, xfy, or). % linkable infix :- op(770, xfy, and). % linkable infix :- op(700, xfx, <=). % infix operator :- op(700, xfx, include). % infix operator
Because we use operators the rules are still Prolog terms and can be compiled directly with a Prolog compiler, resulting in much faster code.
There are special facts, called 'system', which are used to specify the location of various files, whether debug tracing is enabled and specific attributes of web servers.
Facts are gathered from the user via fill-in-the-blank, multiple choice and yes/no questions (see Figures 1 and 2). For example, this is how we ask for the 'xplSize' attribute. The 8 is the HTML form field length.
attr(xplSize, [ prompt = $How big is your .XPL file in bytes?$, ask = field, length = 8 ]).
This is how we ask for a value from a list.
attr(environmentNameVer, [ prompt = $What environment are you running under?$, ask = menu(['Windows 3.x', 'Windows 95', 'Windows NT', 'DOS', 'Linux']) ]).
This is how we ask a yes/no question:
attr(iniStringEscOff, [ prompt = $Is string_esc off in your .INI file or Prolog program?$, ask = yesno ]).
Facts can also have multiple values. If you wanted to ask for all the environments you create a multivalued fact as shown below. This allows the user to select multiple values from the list using the [ctrl] key and the mouse. You use the 'include' comparator to check multivalued facts (see If-Then Rules below).
attr(environmentNameVer, [ multivalued, prompt = $What environment are you running under?$, ask = menu(['Windows 3.x', 'Windows 95', 'Windows NT', 'DOS', 'Linux']) ]).
The most important optimization in WebLS is reducing the number of interactions with the user. To do this, the logic-base supports the concept of related facts. They make it possible to 'group' facts together, so all the facts for a particular hypothesis are gathered in a single form.
Related facts are specified using Prolog lists as follows:
attr(environmentNameVer, [ prompt = $What environment are you running under?$, ask = menu(['Windows 3.x', 'Windows 95', 'Windows NT', 'DOS', 'Linux']), related = [memSize, processorType] ]).
This relates 'memsize' and 'processorType' to 'environmentNameVer'. If WebLS is going to ask the user for a value for environmentNameVer, it will also ask for the other two on the same form.
Rules are the heart of the logic-base. Usually they directly determine the problem. They can also be used to determine other facts. Here is an example:
if errorMessage = 'Redefining system or compiled predicate' then problem = redefiningPredicate.
The rules are executed by a custom inference engine. Before we look at it, lets look at some more rules and how answers are outputted.
if environmentNameVer = 'Windows 3.x' or environmentNameVer = 'Windows NT' or environmentNameVer = 'Windows 95' then environment = 'Windows'.
This rule distills the fact 'environment' from the user-provided fact 'environmentNameVer'. This allows us to simplify rules such as the following:
if environment = 'Windows' and programType = 'Amzi! Sample Program' and errorMessage = '[ODBC] Datasource name not found' and (amziSampleName = 'dbgene' or amziSampleName = 'dbgenevb') then problem = geneNotRegistered.
Note the WebLS inference engine supports the use 'and', 'or' and parenthesis to check facts. Not shown here, is the ability to check if an fact is 'not equal' to a particular value. You can use these comparators: =, \= (not equal), >, <, >= and <=. For checking multivalued facts use 'include' as follows:
if (languageTool = 'Borland C++' or languageTool = 'Borland C') and symptoms include 'Errors linking with the Logic Server libraries' and applicationMode = '16-bit' and releaseDate <= '19960302' then problem = borlandStatic16.
Rules that have determined an answer end with 'problem=' (although this is configurable in the system variables). The final piece of the logic-base is outputting the answer that corresponds to the problem.
For each fact in the rule-base we had to define a predicate to obtain its value. Similarly for every problem, we have to define how to output the answer (see Figure 3). WebLS offers three options.
First, is simply outputting HTML text:
answer(dcgVariableGoal, [ text = [$This can be caused by attempting to compile a DCG $, $statement with a variable as a goal, e.g. $, $sentence-->noun, X, verb.$] ]).
When the problem is 'dcgVariableGoal', the list of text is outputted along with a proper HTML header.
Second, if the answers are kept in separate HTML documents, they can be sent out as shown in this example:
answer(odbcNotInitialized, [ htmlFile = 'odbcIntro.htm' ]).
The third option is to redirect the user to another document (or URL, Universal Resourece Locator) [HTTP]. This allows the answers to be kept in regular HTML documents and use HTML tags (bookmarks) to locate the right place in the document. For example:
answer(componentNotInstalled, [ url = 'http://www.amzi.com/install#delphi_component' ]).
This URL is for the file 'install' on the 'www.amzi.com' web site; the location in the document is the tag 'delphi_component.'
Another feature of answers is that you can also define a list of notes to accompany the answer. Notes are outputted after the answer separated by a horizontal rule. Notes are defined like text answers as follows:
note(debugEmbed, [ text = [$For more information on debugging embedded Prolog modules $, $we suggest you see <A HREF="ftp://ftp.amzi.com/pub/articles/APIDEBUG.TXT">Debugging Hints</A>.$] ]).
Notes are used by adding a notes list to the answer. Remember lists are enclosed by square brackets. For example:
answer(cLargeModelRequired, [ text = [$16-bit C/C++ applications require the large memory model. $, $Failure to use it leads to immediate GPFs.$], note = [debugEmbed, cLibraries] ]).
The C code (amzicgi.c, amzisub.c) performs a number of critical functions across these diverse environments. It:
The C code is also responsible for starting the Amzi! Logic Server, loading the WebLS application and running it. Many of the functions listed above are actually implemented as extended predicates in Amzi! Prolog.
For example, the code for getPrivateProfileSection/3 that reads the INI file calls the Windows function GetPrivateProfileSection which returns all the values in one long string. getPrivateProfileSection/3 parses that string into a list of facts of the form 'fact(key, value)', which is returned by the predicate. This implementation gives the Windows function 'GetPrivateProfileSection' a more natural Prolog interface.
TF EXPFUNC p_GetPrivateProfileSection(ENGid eid) { ... /* Call the Windows function to read the INI file */ ecode = GetPrivateProfileSection(sSection, sValue, MAX_PROFILE, sFile); /* Check if anything was read */ if (ecode == 0 || ecode == MAX_PROFILE - 1 || ecode == MAX_PROFILE -2) *sValue = '\0'; /* Make a new Prolog list */ rc = lsMakeList(eid, &plist); /* Loop adding all the facts to the list */ p = sValue; while (*p != '\0') { /* Build a string containing fact( */ strcpy(termStr, "fact('"); /* Find the = sign and take the key */ v = strtok(p, "="); slashslash2(sBuf, v); strcat(termStr, v); strcat(termStr, "', '"); /* Get the rest as the value */ p = p + strlen(v); p++; slashslash2(sBuf, p); strcat(termStr, sBuf); strcat(termStr, "')"); /* Put fact(Attr, Value) on the list if there was a value */ if (strlen(p) > 0) { /* Convert the string to a Prolog Term */ rc = lsStrToTerm(eid, &pitem, termStr); /* And add it onto the list created earlier */ rc = lsPushList(eid, &plist, pitem); } p = p + strlen(p); p++; } /* Unify the list with the second parameter to the predicate */ tf = lsUnifyParm(eid, 2, cTERM, &plist); }
The Prolog code (amzicgi.pro) provides a library of functions commonly needed for CGI programs. It:
It was not always clear which functions belonged in the CGI Interface as opposed to the Inference Engine. However, the primary design criteria was to keep the CGI Interface completely general and useful to any Prolog CGI program.
Lets examine each of these steps in detail showing some of the Prolog code used to implement them.
The initial form (see Figure 1) gathers enough facts to determine a general direction to search for the answer. The names of the facts are simply taken from the name of the form field. For example:
<SELECT NAME="apiFunction" ALIGN=left> <OPTION>lsInit <OPTION>lsGetParmType <OPTION>lsGetArgType </SELECT>
This gets the value for 'apiFunction' from the specified list.
WebLS writes the initial form and exits. When the user presses the submit button (on the form), WebLS starts again and the form values (new facts) are read via the CGI interface.
When WebLS starts again it asserts the new facts (and those saved in hidden fields) to Prolog's dynamic database. Next, the logic-base is executed to see if a rule can be found, whose facts are all known, and whose values match the ones specified in the rule. To do this, the inference engine uses Prolog's built-in backtracking search. It starts with the first rule in the logic-base and continues to examine the rules until one is found or there are no more rules. If the problem is identified, the answer is displayed for that problem and we proceed to the clean-up phase (see Figure 3).
The heart of the inference engine is as follows:
% getAV, getNotAV, prove and checkFact implement a backward chaining % inference engine. All of these predicates pass a 'needs' list up % and down. This list contains attribute:value pairs that are needed % to prove a particular hypothesis. If the needs list is empty, then % the hypothesis is true. % % Tries to find a rule for the specified attribute, Attr. Once it % finds a rule, it tries to prove its conditions. getNotAV is used when % trying to check if a value is not equal. % getAV( Attr, Value, Na, N, D ) :- if Conditions then Attr = Value, logCallFail([Attr, $ = $, Value, $\n$], D), DD is D + 3, prove(Conditions, Na, N, DD), logExitRedo([Attr, $ = $, Value, $\n$], D). getAV( Attr, Value, Na, N, D ) :- not( if _ then Attr = _ ), checkFact(Attr, Value, Na, N, D). getNotAV( Attr, Value, Na, N, D ) :- if Conditions then Attr = X, Value \= X, logCallFail([Attr, $ \\= $, Value, $\n$], D), DD is D + 3, prove(Conditions, Na, N, DD), logExitRedo([Attr, $ \= $, Value, $\n$], D). getNotAV( Attr, Value, N, N, D ) :- fact(Attr, X), !, Value \= X. getNotAV( Attr, Value, Na, N, D ) :- not( if _ then Attr = _ ), checkFact( Attr, X, Na, N, D). % % prove handles the operators: =, \=, and, or, <, >, <=, >=, include % prove( Condition and Rest, Na, N, D ) :- prove(Condition, Na, Nnext, D), prove(Rest, Nnext, N, D). prove( Conditions1 or Conditions2, Na, N, D ) :- prove(Conditions1, Na, N, D) ; prove(Conditions2, Na, N, D). prove( Attr = Value, Na, N, D) :- cgiLog(tab(D)), cgiLog([$Trying $, Attr, $ = $, Value, $\n$]), getAV(Attr, Value, Na, N, D), cgiLog(tab(D)), cgiLog([$Matching $, Attr, $ = $, Value, $\n$]). prove( Attr \= Value, Na, N, D ) :- cgiLog(tab(D)), cgiLog([$Trying $, Attr, $ \\= $, Value, $\n$]), getNotAV(Attr, Value, Na, N, D), cgiLog(tab(D)), cgiLog([$Matching $, Attr, $ \\= $, Value, $\n$]). prove( Attr < Value, Na, N, D ) :- getAV(Attr, Aval, Na, N, D), cgiLog(tab(D)), cgiLog([$Trying $, Attr, $ < $, Value, $\n$]), Aval @< Value, cgiLog(tab(D)), cgiLog([$Matching $, Attr, $ < $, Value, $\n$]). prove( Attr > Value, Na, N, D ) :- getAV(Attr, Aval, Na, N, D), cgiLog(tab(D)), cgiLog([$Trying $, Attr, $ > $, Value, $\n$]), Aval @> Value, cgiLog(tab(D)), cgiLog([$Matching $, Attr, $ > $, Value, $\n$]). prove( Attr <= Value, Na, N, D ) :- getAV(Attr, Aval, Na, N, D), cgiLog(tab(D)), cgiLog([$Trying $, Attr, $ <= $, Value, $\n$]), Aval @=< Value, cgiLog(tab(D)), cgiLog([$Matching $, Attr, $ <= $, Value, $\n$]). prove( Attr >= Value, Na, N, D ) :- getAV(Attr, Aval, Na, N, D), cgiLog(tab(D)), cgiLog([$Trying $, Attr, $ >= $, Value, $\n$]), Aval @>= Value, cgiLog(tab(D)), cgiLog([$Matching $, Attr, $ >= $, Value, $\n$]). prove( Attr include Value, Na, N, D ) :- getAV(Attr, Aval, Na, N, D), cgiLog(tab(D)), cgiLog([$Trying $, Attr, $ include $, Value, $\n$]), member(Value, Aval), cgiLog(tab(D)), cgiLog([$Matching $, Attr, $ include $, Value, $\n$]). % % Checks the value of a fact and succeeds or fails % checkFact(Attr, Value, N, N, D) :- fact(Attr, X), % is there a known value for this attribute? !, % if so don't ask again X = Value. % succeed or fail based on the expected value. checkFact(Attr, Value, Na, [Attr:Val|Na], D).
The inference engine is invoked for first time as follows:
getAV(problem, Name, [], [], 0), % in body of cgiMain/0
This looks for a rule whose right-hand side has 'problem = <Name>' and returns the name. The two empty lists mean we are looking for a rule to match all the facts; in other words no more facts are needed to satisfy the rule.
For example, given the following facts and rule:
fact(programType, 'Amzi! IDE'). fact(errorMessage, 'Control stack full'). if programType = 'Amzi! IDE' and errorMessage = 'Local stack full' or errorMessage = 'Control stack full' then problem = ideStack.
Then the call to getAV/5 would return:
getAV(problem, ideStack, [], [], 0), % in body of cgiMain/0
If the problem is not found, then we build a new form (a field at a time) to gather more facts from the user.
To build the next form, we search the logic-base again. This time we look for the first rule whose known facts all match, and make a list of the unknown facts whose answers are 'needed' to prove it. To do this, getAV/5 is called with a NeedsList (instead of the empty list) as shown:
getAV(Goal, Name, [], NeedsList, 0), !, % in body of cgiMain/0 needsAskAll(NeedsList),
The NeedsList is passed to needsAskAll which outputs the HTML representing question according to the attr/2 definition for each fact on the list. It also outputs questions for each fact on the related list of each fact on the NeedsList. The resulting form will be returned to the web server to be displayed to the user (see Figure 2).
For example, given the following facts and rule:
fact(programType, 'Amzi! IDE'). fact(environmentNameVer, 'Windows 95'). if programType = 'Amzi! IDE' and environmentNameVer = 'Windows 95' and ideCommand = 'Listener/(Re)consult' and iniStringEscOff = 'Yes' then problem = ideStringEscWin95.
Then the call to getAV/5 would return:
% in body of cgiMain/0 getAV(problem, ideCmpUnusedVars, [], [ideCommand, iniStringEscOff], 0),
This approach of asking for all the unknown and related facts at once differs from normal expert systems which just ask for one thing at a time. It is also the major reason WebLS uses a custom inference engine.
Other approaches were considered before settling on this one. It may be desirable to gather all possible hypotheses instead of just the first one. The disadvantage of this approach is that too many irrelevant questions might be asked. Instead, WebLS relies on:
As we gain more experience and feedback with WebLS, we'll be able to evaluate this more closely. In any case, the architecture of the inference engine makes it trivial to backtrack into getAV/5 to obtain a list of all possible hypotheses and needs lists.
Before WebLS exits, the facts we already know are outputted to the HTML form as hidden fields. This ensures they will be read back in by the CGI Interface when the form is submitted.
When WebLS starts again, it asserts the new facts from the user's form and the old facts from the hidden fields. Then it checks for an answer again. This process repeats until we find the problem or all the rules fail.
Finally when WebLS has found an answer (or failed to find one), the name of the answer and all the facts are recorded in a file. This file is actually a set of Prolog terms that can be consulted into a Prolog program for later analysis of the effectiveness of the system.
When tracing is enabled by turning on or off a system variable in the dynamic database, a URL pointing to the output is included on each page generated by WebLS. This allows the trace to be examined directly in the web browser.
The trace is created by placing calls to logCallFail/2 and logExitRedo/2 in the appropriate places in getAV/5. D is the depth parameter and is used to indent recursive calls in the trace making them easier to read. For example, the first clause of getAV/5 with tracing and the depth parameter now looks like this:
getAV( Attr, Value, Na, N, D ) :- if Conditions then Attr = Value, logCallFail([Attr, $ = $, Value, $\n$], D), DD is D + 3, prove(Conditions, Na, N, DD), logExitRedo([Attr, $ = $, Value, $\n$], D).
The trace predicates are implemented simply as:
logCallFail(Stuff, D) :- cgiLog(tab(D)), cgiLog($Trying $), cgiLog(Stuff). logCallFail(Stuff, D) :- cgiLog(tab(D)), cgiLog($Failing $), cgiLog(Stuff), fail. logExitRedo(Stuff, D) :- cgiLog(tab(D)), cgiLog($Matching $), cgiLog(Stuff). logExitRedo(Stuff, D) :- cgiLog(tab(D)), cgiLog($Retrying $), cgiLog(Stuff), fail.
Note that the second clause of each fails so as to not interrupt Prolog's normal backtracking flow.
Here are a couple of sample rules and the resulting trace output:
:- op(790, fx, if). % prefix operator :- op(780, xfx, then). % infix operator :- op(775, xfy, or). % infix that can be linked :- op(770, xfy, and). % infix that can be linked :- op(700, xfx, <=). % infix operator :- op(700, xfx, include). % infix operator system('Log File', '/usr/me/html/liltrace.htm'). system('Log File URL', 'http://www.company.com/liltrace.htm'). system('Results File', $/usr/me/logs/lilrslts.pro$). system('Initial Form', $/usr/me/html/lilrules.htm$). system('Temp Directory', $/usr/me/temp/$). system('Body Args', $bgcolor=#CEFAFF text=#000000$). system('Title', $Amzi! Little Problem Resolver$). system('Footer', $<FONT SIZE=-1>Copyright ©1996 Amzi! inc.$). system('Form Action', 'Executable Path'). system('Goal', 'problem'). attr(errorCode, [ prompt = $What error code was displayed or 'none'?$, ask = field, length = 5 ]). attr(programType, [ prompt = $What program are you running?$, ask = menu(['other', 'Amzi! Hello Program', 'Amzi! Sample Program', 'Amzi! IDE', 'My Program', 'Windows Application']) ]). if errorCode = '600' and programType = 'Amzi! Hello Program' then problem = helloXPL. if errorCode = '600' and programType = 'Amzi! Sample Program' then problem = sampleXPL. if errorCode = '600' and programType = 'My Program' then problem = missingXPL. answer(helloXPL, [ text = [$The program is unable to locate the Amzi! Prolog object module, an $, $XPL file. Make sure the AMZI installation directory is in your $, $path, and that HELLO.XPL file exists in the path or the current $, $directory.$] ]). answer(sampleXPL, [ text = [$The program is unable to locate the Amzi! Prolog object module, $, $an XPL file. Some of the Amzi! samples include source only $, $and the PRO file needs to be compiled and linked $, $into an XPL file.$, $<P>If the XPL file exists, then it must be in your $, $path or the current directory.$] ]). answer(missingXPL, [ text = [$The program is unable to locate the Amzi! Prolog object module, $, $an XPL file. Make sure the the XPL file exists in your path $, $or the current directory.$, $<P>Note: Some development environments such as Visual Basic and $, $Delphi set the current directory to a directory you might not $, $have expected.$] ]).
You can see in Figure 5 above the three main parts of the logic-base. There are the rules. These are preceded by the attr/2 definitions and followed by the answer/2 definitions.
system($Input File$,$C:\WEBSITE\CGI-TEMP\287WS.INI$). system('Content File','C:\WEBSITE\CGI-TEMP\287WS.INP'). system('Output File','C:\WebSite\cgi-temp\287ws.out'). . . . system('Log File','C:\AmziCGI\logs\lilrun.htm'). system('Title',$Amzi! Problem Resolver$). system('Initial Form',$C:\AmziCGI\html\rptprob.htm$). system('AmziCGI Directory',$C:\AmziCGI$). system('Form Action','Executable Path'). system('Goal',problem). cgi('Content Length','191'). cgi('Content Type','application/x-www-form-urlencoded'). . . . cgi('Executable Path','/cgi-win/cgirun.exe'). cgi('Request Method','POST'). cgi('Request Protocol','HTTP/1.0'). fact(submitProblem,'Submit'). fact(amziVersion,'3.3 Jan96'). fact(environmentNameVer,'Windows 3.x'). fact(apiFunction,none). fact(predicate,none). fact(languageTool,other). fact(programType,'Amzi! Sample Program'). fact(errorCode,'600'). fact(errorMessage,none). Processing POST method (subsequent times through) Opening fact file: submitProblem ---> Calling logic-base for the first time Trying problem = helloXPL Checking if errorCode = 600 Matching errorCode is 600 Checking if programType = Amzi! Hello Program Failing problem = helloXPL Trying problem = sampleXPL Checking if errorCode = 600 Matching errorCode is 600 Checking if programType = Amzi! Sample Program Matching programType is Amzi! Sample Program Matching problem = sampleXPL ---> Logic-base succeeded in finding an answer: sampleXPL
When debugging new rules, the trace facility (see Figure 6) is very useful for figuring out which rule succeeds and why. In addition to the trace facility, WebLS reports syntax errors in the logic-base (with a custom handler for read errors) and when attr/2 is not defined for a needed fact and when answer/2 is not defined for a reached conclusion. These errors are returned on an HTML error page designed for the purpose.
Handling these errors is accomplished in Prolog by using catch and throw. The catch is in the main/0 predicate:
catch(cgiMain, X, cgiError(X)), % in body of main/0
This catches any error (which unifies with X) and calls cgiError/1 to generate the error message page. The throws look like the following and are used anytime attr/2 or answer/2 are queried.
(answer(Name, AnsList) -> true ; throw([$answer($, Name, $... is not defined in your logic-base<P>$]) )
If the call to answer fails then the error message list is thrown back to the catch in main/0.
WebLS has other possibilities for the future:
[HTML] "HTML 2.0 Proposed Standard", Internet Engineering Task Force, HTML Working Group.
[HTTP] "HTTP/1.1 Internet Draft", W3C and Internet Engineering Task Force.
[Merritt, 96] Merritt, D. 1996, "Building Custom Rule Engines", PC AI, Mar/Apr 96.
[Merritt, 94] Merritt, D. 1994, "Deceptive User Interfaces Impede AI", AI Expert, Aug 94.
[Sehmi, 95] Sehmi, Arvindra, "Amzi! Prolog + Logic Server 3.3-Opening the "Closed World" of Prolog", PC AI, Sep/Oct 95
[Tarau, 95] Bin Prolog 4.0, Departement d'Informatique Universite de Moncton
[WINCGI] "Windows CGI 1.3 Interface", O'Reilly and Associates.
Arvindra Sehmi is a Senior Systems Analyst, Global IT Infrastructure, at SBC Warburg - A Division of Swiss Bank Corporation. He holds a Ph.D. in Biomedical Engineering from Leicester University, U.K., and is nearing completion of an MBA in the Business School of the Open University. He can be reached at ASehmi@ieee.org.
Mary Kroening is a founder of Amzi! inc., and is developing WebLS and other interfaces between Internet resources and the Amzi! Logic Server. She can be reached at mary@amzi.com or at Amzi! inc., 40 Samuel Prescott Drive, Stow, MA 01775 U.S.A., tel 508-897-7332, fax 508-897-2784.