/* BUG.PL */ /* :- module bug. :- public bug/1, bug/2, warning/1, warning/2, assertion/1, assertion/2, assertion/3, '??' /1. */ :- op( 100, fx, ?? ). /* SPECIFICATION ------------- This module exports 'bug'. I call this to report "shouldn't happen" errors, such as clauses that fail when they ought to succeed. 'bug' reports the bug and aborts. It also exports 'assertion', a variant of 'bug' that takes the condition as an argument. Use this to state an assertion (i.e. program invariant) that should hold at some point in your program. ?? is like assertion/1, but an operator declared for use when you need to find out which goal in a sequence of goals has failed when it ought to have succeeded. Put ?? in front of each goal, and re-run the predicate. NOTE WELL: This version of the module outputs MY address. Please replace it by your own, since in general bugs will get reported to your students or other users of the Tutor. Report any bugs you think I ought to know about to me, but try to fix them first. PUBLIC bug( Culprit+ ): ----------------------- Report a program bug. Culprit will usually be the value that provoked the bug: for example, an illegal argument value. It could also be a list of culprits. In Prolog they're all terms anyway, and 'bug' just calls 'write' to display them. 'bug' writes Culprit to an output stream that's guaranteed to be accessible and visible to the user (the 'user' stream on my system), gives details of how to contact the bug-fixer, and aborts to the top-level interpreter. If your Prolog implements backtrace (mine doesn't), it's worth calling it from the body of bug, to give you a trace of the calls leading up it. PUBLIC bug( Where+, Culprit+ ): ------------------------------- As bug/1, but writes Where just before writing Culprit, and writes the word 'Culprit(s)' before Culprit. PUBLIC warning( Culprit+ ): PUBLIC warning( Where+, Culprit+ ): ----------------------------------- Like bug, but don't halt after reporting. PUBLIC assertion( Cond+ ): -------------------------- Tests Cond and reports a bug iff it fails. Call this to specify invariants that hold between your variables or facts, passing the invariant as Cond. PUBLIC assertion( Cond+, Term+ ): --------------------------------- As assertion/1, but writes out Term as additional information. PUBLIC assertion( Cond+, Term+, Culprit+ ): ------------------------------------------- As assertion/1, but writes out Term and Culprit as additional information. PUBLIC ?? Goal+ --------------- Like assertion/1, but writes a slightly different message saying that ?? detected a failure. More notes on their use. ------------------------ If you look at a well-written program, you will find constructions like this if i=2 then action1 else if i=3 then action2 else if i=5 then action3 else Bug( 'In procedure P', Illegal value of i', i ); end using some general routine to report an illegal value and stop the gracefully. If the language implements a case statement, a similar call can be used to trap default values not in the list of cases. The authors of ``Software Tools'' (Brian W. Kernighan and P. J. Plauger.) an excellent book on how to write good programs and good tools, lay great stress on this under the name of ``defensive programming'': trapping bugs _before_ they cause a program to run wild and waste pages of printer paper or hours of computer time. Prolog is not quite as horrible as Fortran. But the technique is worth adapting, especially to catch unexpected clause failures. These are often caused by errors that lead to illegal arguments being passed: do_command( again ) :- do_again. do_command( bye ) :- do_bye. ... do_command( show(Range) ) :- do_show( Range ). do_command( Other ) :- bug( 'do_command', Other ). If an illegal value were somehow passed to do_command and the final clause weren't present, do_command would fail, perhaps provoking unexpected backtracking much later on and causing the program to run amok in a fashion having little apparent connection with the illegal value. It's neater to trap, and report, the bug as soon as it's detected. It would be even neater to be able to tell the Prolog compiler that certain predicates should raise an error whenever they fail: much as one can do with the NOFAIL control card for Snobol patterns. I leave this as a suggestion for Prolog implementors. By convention, I use predicates bug/1 and bug/2 for this. Their actions are the same except that bug/2 takes one extra argument, usually a message giving extra information about where the bug was detected. After reporting the bug, they both call abort rather than halt. This leaves Prolog running, so giving the programmer some chance of discovering more about the bug's environment. Poplog Prolog lacks the standard predicate backtrace. If your Prolog implements backtrace, amend bug to call it; this will give you information about the path leading up to the bug. This module also exports assertion1/2/3. The idea is that they be used as ``active annotations'' to indicate that the specified condition should always hold at this point: assertion( Length1=Length2, 'list lengths should be equal' ) A good program will note such invariant conditions in its comments. assertion gives the added bonus of signalling violations. One place that I have found assertions particularly useful is in situations such as p( X ) :- q( X ), fail. p( _ ). where we want to call q and then undo the bindings it makes. There is an example of this in module show. If some mistake causes q to fail when it ought to succeed, this will not be visible in the control behaviour, since p will succeed regardless. By changing the call of q(X) to assertion(q(X)), we can trap such mistakes. */ /* IMPLEMENTATION -------------- 'bug' writes the message and name of person to contact on 'user' and halts. In my version, this module also defines a Pop-11 routine for enabling 'bug' to be called from Pop-11. You can omit this if not using Poplog! 'assertion' must call Cond in such a way that any bindings are preserved. I.e. not inside a 'not'. */ /* Load Pop-11 code in BUG.P. Omit this if not using Poplog. */ :- pop_compile( '$eden_src/bug.p' ). bug( X ) :- bug_1( '*** BUG detected ***', '', '', X, '', '' ), abort. bug( X, Culprit ) :- bug_1( '*** BUG detected ***', '', '', X, '\nCulprit(s): ', Culprit ), abort. warning( X ) :- bug_1( '*** BUG detected ***', '', '', X, '', '' ). warning( X, Culprit ) :- bug_1( '*** BUG detected ***', '', '', X, '\nCulprit(s): ', Culprit ). assertion( Cond ) :- Cond, !. assertion( Cond ) :- bug_1( '*** BUG (assertion violation) detected ***', 'Failing condition was ', Cond, '', '', '' ), abort. ??( Cond ) :- Cond, !. ??( Cond ) :- bug_1( '*** ?? goal failed ***', 'Failing condition was ', Cond, '', '', '' ), abort. assertion( Cond, X ) :- Cond, !. assertion( Cond, X ) :- bug_1( '*** BUG (assertion violation) detected ***', 'Failing condition was ', Cond, X, '', '' ), abort. assertion( Cond, X, Culprit ) :- Cond, !. assertion( Cond, X, Culprit ) :- bug_1( '*** BUG (assertion violation) detected ***', 'Failing condition was ', Cond, X, '\nCulprit(s): ', Culprit ), abort. /* bug_1( Type+, CondMsg+, Cond+, X1+, X2+, X3- ): Utility for writing bits of messages. The COS is assumed to be where you want the bug report to go. */ bug_1( Type, CondMsg, Cond, X1, X2, X3 ) :- telling( COS ), tell( user ), write( Type ), nl, ( CondMsg \= '' -> write(CondMsg), write(Cond), nl ; true ), write(X1), write(X2), write(X3), nl, display_contact_address, tell( COS ). /* display_contact_address: Write details to COS of the person to receive bug reports. */ display_contact_address :- write( 'Please report the bug to Jocelyn Paine,' ), nl, write( 'Experimental Psychology, South Parks Road, Oxford OX1 3UD.' ), nl, write( 'Email: POPX @ UK.AC.OX.VAX.' ), nl. /* :- endmodule. */