/*  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( '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 Ireson-Paine,' ), nl,
    write( 'Experimental Psychology, South Parks Road, Oxford OX1 3UD.' ),
    nl,
    write( 'Email: POPX @ UK.AC.OX.VAX.' ), nl.

/*
:- endmodule.
*/
