Debugging Your Programs

There are a number of tools in addition to the Source Code Debugger that help track down bugs in embedded and standalone Prolog programs.

Initial Development for Embedded Modules

When you are first developing your Prolog components, you can usually create a suite of predicates that simulate the way the Prolog code will be called from a host language.

By using a simulated front-end written in Prolog, you can get most of the Prolog code working correctly using the normal development tools of the Windows-IDE.

You can also create Prolog versions of any extended predicates you write, where the Prolog versions simply return values that simulate the results you expect returned by your extended predicates.

Tracking Prolog Errors

There are a number of ways to gather information from a running Prolog program. These are described in this section.

Logging

The most basic debugging tool is the write statement. In the case of an embedded Prolog module, especially in a Windows environment, the best way to add 'write's is to use the logging capabilities of Amzi!.

You can specify that a logfile be opened either from the Prolog program:

or from the .cfg file. For example, if your .xpl file is foo.xpl then you can add this line to your foo.cfg file:

Once you've got a log file you can write to it using the writelog/1 predicate and the nllog/0 (for newlines) predicate.

You close the logfile, although it will close automatically, with:

Debugger-Like Output

If you're having trouble with the flow-of-control in the running program, you can add some special statements that provide output similar to the debugger's for each of the four ports, CALL, EXIT, FAIL, and REDO.

To do this simply put the operator '?' in front of lines that you want to create output for. Or, use the bug/1 predicate like a call/1 predicate.

For example, if you suspected problems with goal2 below, the ? operator will generate diagnostic information for each of the ports. It will work for both compiled and interpreted code.

The following debugging predicates are built into Amzi!

buginit/1 - optionally opens a file to which debugging output is directed. The argument is the file name. If not specified, output is directed to standard output.

buginit/0 - optionally opens file 'bug.log' for debugging output.

user_bugwrite/1 - The user can define an output predicate that is used by bug/1 to output a term, and most likely follows it with a newline. If not specified, output is generated with 'writeq' statements to the debugging output stream.

bug/1 - brackets a call to a predicate with debugging information

?/1 - an operator synonym for bug/1.

bugclose/0 - closes the debugging file, if one was open

flow/1,3 - can be used to note the flow of control at a point in the program, for both normal code and DCG.

catch and throw

These predicates allow you to implement better error handling in your own code. catch/3 lets you make a normal call to a goal with the added feature that it also 'catch'es any exceptions that are 'throw'n further down the code. This makes it easier for you to implement your own error catching code. For example:

main :- 
     catch(
          dostuff,
          error(X,Y), 
          process_err(X,Y) ).

process_err(badpredarg, X) :- 
     write($Bad argument given to predicate: $), 
     write(X), nl. 
process_err(X, Y) :- 
     write($Some other error: $), 
     write(X:Y), nl.

dostuff :- foo1, foo2, ...

foo2 :- 
     getfromsomewhere(X), 
     foon(X), ...

foon(case1) :- ... 
foon(case2) :- ... 
... 
foon(X) :- throw(error(badpredarg, foon(X)).

See the example samples/prolog/misc/catch.pro and See Flow of Control Predicates for more details.

Prolog Error File

Sometimes error messages are not retrieved fully from an executing Prolog .xpl file. You can often look in the file foo.err, where foo is the name of the .cfg or .xpl file, to find the error messages emitted during a run.

This same file can be useful in the IDE as well. The listener writes its messages to wideA.err (or wideW.err) and the compiler writes messages to acmp.err. These correspond to the two .xpl Files that are run by the IDE.

The existence of the error file does not eliminate the need to check error conditions from Logic Server API calls.

Writing Your Own Embedded Debugging Predicates

Sometimes you need to build a custom version of the built-in debugging predicates. This is especially true with a program that is recursive or heavily looping. You can build into your program specialized debugging displays, that are application-specific.

One approach is like this:

trace_open(FILENAME) :- 
fopen(H, FILENAME, w),
assert(trace_file(H)).

trace_close :-
retract(trace_file(H)),
fclose(H).

trace(X) :-
trace_file(H), !,
write('TRACE: '),
write(H,X), nl,
fflush(H). % this if you want to make sure the output is there
trace(_).

Then in your code:

some_tricky_predicate(A,B,C) :- 
  trace(calling:the_tricky_bit(B)), 
  the_tricky_bit(B),    
  trace(exiting:the_tricky_bit(B)), ... 

Now if you call trace_open/1, you'll get tracing information written in your trace file, but if you don't the program will quietly do nothing on the trace statements. And you can make this more sophisticated, with other arguments defining level of trace, that make decisions as to what to display in what situations. And you can prune things out:

something(B) :- 
  ( B > 10, trace( bigB(B) ); true ), 
  something_else(B),    ...  

or create a trace with a two argument predicate that is conditional:

trace(CONDITION, X) :- 
  trace_file(H), 
  call( CONDITION ), !, 
  write(X), nl. 
trace(_,    _). 

So the above call becomes:

something(B) :- 
  trace( B > 10, bigB(B) ), 
  something_else(B), ... 

And of course backtracking versions that actually execute the predicate being debugged (which is how the bug/1 and ?/1 predicates are implemented in the system):

trace(X) :-
  trace_callfail(X),
  call(X),
  trace_exitredo(X).

trace_callfail(X) :- 
  trace_file(H), !, 
  ( write('CALL: '), write(X), nl ; write('FAIL:    '), 
  write(X), nl, fail ). 
trace_callfail(X). 
trace_exitredo(X) :- 
  trace_file(H), !, 
  ( write('EXIT: '), write(X), nl ; write('REDO: '), 
  write(X), nl, fail ).    
trace_exitredo(X). 

This is called like this:

some_tricky_predicate(A,B,C) :- 
  trace(the_tricky_bit(B)), ... 

This type of approach would let you see the operation in detail, but with a trace/debugging output that reflects application processing rather than Prolog processing.

Tracking LSAPI Errors

It is often the case that the Prolog program is running just fine, but the Logic Server API calls are not communicating with it correctly.

The various techniques listed in the above section can be used by the Prolog program to indicate that it has, indeed, been called correctly from the host program, and to verify that the terms being passed back to the host program are the correct terms.

Given that Prolog is working correctly, then the problem is somewhere in the LSAPI calls that are retrieving the information.

Tracing LSAPI Calls

To help debug host applications calling the Logic Server API, we've added an API trace facility. You initiate it by setting the .cfg parameter 'apitrace' to 'on'. You must specify a 'logfile' as well, for the trace output goes to the log file.

If you specify 'apitrace' in amzi.cfg, then you'll catch errors in the lsInit() call if there are any. If you specify it in myprog.cfg, where 'myprog' is the name of your .xpl file, you'll get trace information as long as the initialization completed OK.

What follows is some apitrace output from the xgene.c sample program.

lsInit(0, "xgene")
  returns OK
lsLoad(0, "xgene")
  returns OK
lsStrToTerm(0, 00442380, "sibling(mary, Y)")
  returns OK
lsCall(0, 00442380)
  returns TRUE,
  term bound to "sibling(mary,mary)"
lsGetFA(0, 004b7060, 00442358, 00442384)
  returns OK,
  functor set to "sibling" arity 2
lsGetArg(0, 004b7060, 1, 1, 004422e0)
  returns 0
  arg 1 = mary
lsGetArg(0, 004b7060, 2, 1, 00442330)
  returns 0
  arg 2 = mary
lsRedo(0)
  returns TRUE
lsGetFA(0, 004b7060, 00442358, 00442384)
  returns OK,
  functor set to "sibling" arity 2
lsGetArg(0, 004b7060, 1, 1, 004422e0)
  returns 0
  arg 1 = mary
lsGetArg(0, 004b7060, 2, 1, 00442330)
  returns 0
  arg 2 = kathy

Checking LSAPI Error Codes

In the API calls, make sure you always check for the appropriate return codes. Add an if statement that stops when an API error is encountered. In that code you can get error message using the API call lsGetExceptMsg(). For read errors you can also get the input read buffer using lsGetExceptReadBuffer().

Examining Prolog Terms

If you are doing hacking of Prolog terms, you can verify that the terms are what you expect by using lsTermToStr(). It converts a term into a string, just as a write(X) would write whatever the term X was from a Prolog listener. You can then look at the string to see if you are picking up the term correctly.

For example:

  tf = lsCallStr(eid, &term, "foo(X,Y)"); 
#ifdef DEBUG 
  lsTermToStr(eid, &term, buffer, 255); 
  printf("Called: %s\n", buffer); 
#endif 
if (tf == TRUE) {       
#ifdef DEBUG    
  lsGetArg(eid, term, 2, cTERM, &targ2);  
  lsTermToStr(eid, &targ2, buffer, 255);  
  printf("Arg2: %s\n", buffer);   
#endif  
  lsGetArg(eid, term, 2, cSTR, arg2);     
  ...do the right thing...       
  } 
else    
  if (tf == FALSE)                
    printf("Call failed\n");        
  else {               
    lsErrMsg(eid, buffer);          
    printf("Prolog error: %d, %s\n", tf, buffer);           
    }
  }

This section of code will report the error messages of any erroneous results, as well as, during debug mode, display the values of various terms during the course of execution.

Copyright ©1987-2011 Amzi! inc. All Rights Reserved. Amzi! is a registered trademark and Logic Server is a trademark of Amzi! inc.