/*   ANSWERS.PL  */
 
 
:- module answers.
 
 
:- public answer/2.
 
 
/*
SPECIFICATION
-------------
 
PUBLIC answer( Goal+, Vars+ ):
 
This predicate is exported to answer the question whose Prolog form is
Goal and whose variables have the names given in Vars. See the
specification for answer_question in module COMMANDS.
*/
 
 
/*
IMPLEMENTATION
--------------
 
answer calls one of answer_logic or answer_prolog, depending on the
mode.
 
On this implementation (Poplog), answer_prolog works by calling the
predicate 'prolog_tli'. This predicate is the Poplog top-level
interpreter. It takes two arguments: a goal to be proved and a list of
variable names. After displaying one solution, it pauses and asks
the user whether she wants another one.
 
answer_logic finds all possible solutions in one go. It uses findall
to return a list of lists of variable bindings.
write_list_of_binding_lists then displays this list, one set of bindings
per line.
 
A note on the use of findall. The "folklore" predicate
    bagof( Pattern, Goal, Result )
backtracks on any variables that are in Goal but not Pattern. So given
the facts
    a( 1, x ).
    a( 2, x ).
    a( 3, y ).
    a( 4, x ).
then
    ?- bagof( P, a(P,Q), Result )
would give one value of Result corresponding to Q=x:
    Result [ 1, 2, 4 ]
and, on resatisfaction, another corresponding to Q=y:
    Result = [ 3 ]
 
In the Tutor, this situation would arise if someone had asked the
question
    a( V, _ )?
The resulting call of bagof from answer_logic would be
    bagof( [ var(V,"V") ], a(V,_), ListOfBindingLists )
and it would behave as above. We must have _all_ possible values of V in
the _same_ ListOfBindingLists. This is achieved if we call
findall instead of bagof.
 
 
Both answer_prolog and answer_logic call protect_from_errors. This takes a
goal and traps errors that may occur when answering it. If we didn't
do this, errors such as
    X is  a +  b
would cause an abort. We want instead for the Tutor to continue,
displaying a message about the error, so we use Poplog's error-handling
mechanism to catch such errors.
*/
 
 
:- needs
    ask_yn / 2,
    for_in_list / 3,
    is_mode / 1,
    once / 1,
    output / 1.
 
 
:- dynamic prolog_error/2.
 
 
answer( Q, Vars ) :-
    is_mode( logic ),
    !,
    answer_logic( Q, Vars ).
 
answer( Q, Vars ) :-
    answer_prolog( Q, Vars ).
 
 
answer_prolog( Goal, Vars ) :-
    protect_from_errors( prolog_tli( Goal, Vars ) ), !.
 
 
answer_logic( Goal, Vars ) :-
    protect_from_errors( findall( Vars,
                                  Goal,
                                  ListOfBindingLists
                                )
                       ),
    write_list_of_binding_lists(ListOfBindingLists,Vars).
 
 
/*
Protecting from errors
----------------------
 
This is specific to Poplog. When an error occurs, Poplog checks to see
if there is a predicate called "prolog_error", with arity two. If there
is, that predicate takes control of the handling of the error. By
default, there is one clause for that predicate, which ambushes errors
caused by undefined predicates. (See HELP ERRORS from Poplog Prolog).
 
So to trap any error that might occur during G, we asserta a new clause
which will write out the cause of the error and then fails, thus causing
G to fail in turn. So as not to alter Prolog error-handling in the rest
of the Tutor, we must retract this clause after we've finished. We do
this using 'once': this enables us to backtrack into protect_from_errors
and backtrack into G without backtracking the 'retract' and possibly
deleting other clauses.
 
With luck, other Prologs will provide a similar error trapping feature.
Use it if you can: even severe errors should not be able to crash out
of the Tutor's read-obey loop.             
 
The specification of protect_from_errors is then
    protect_from_errors( Goal+ ):
solves Goal. If Goal contains an error, protect_from_errors writes a
message saying so and fails. Otherwise it succeeds or fails depending on
what Goal does. It is resatisfiable to the same extent as Goal.
*/
 
 
protect_from_errors( Goal ) :-
    asserta(( prolog_error(M,C) :-
                  M \= 'UNDEFINED PREDICATE',
                  output( 'Question caused error'...M...C~ ),
                  !, fail
 
           )),
    call( Goal ),
    once( retract( (prolog_error(_,_):-_) ) ).
 
protect_from_errors( _ ) :-
    once( retract( (prolog_error(_,_):-_) ) ).
 
 
/*
Writing variable bindings
-------------------------
 
This is done by
    write_list_of_binding_lists( BindingLists+, Vars+ )
BindingLists is the list of bindings from a question; Vars is the list
of variable names it contained. Each element of BindingLists will be a
copy of Vars with some term substituted for each variable.
 
So suppose that the question was
    loves( X, Y )
and we have two solutions
    loves( jason, miranda )
    loves( john, mary )
 
BindingLists will then be
    [ [ var( jason, "X" ),  var( miranda, "Y" ) ],
      [ var( john, "X" ), var( mary, "Y" ) ]
    ]
and Vars will be
    [ var( _, "X" ), var( _, "X" ) ]
 
If BindingLists is empty, there must have been no solutions, i.e. the
question failed, so we write 'No'. Otherwise, if Vars is empty, there
are no bindings to be displayed (and all elements of BindingLists will
be []). In this case, we just write 'Yes'. This is the only thing we use
Vars for - as a quick way of testing how many variables the question
contained.
 
Otherwise, we write 'Yes - ' and then write the binding lists, one per
line.
*/
 
 
write_list_of_binding_lists( [], _ ) :-
    !,
    output( 'No.'~ ).
 
write_list_of_binding_lists( _, [] ) :-
    !,
    output( 'Yes.'~ ).
 
write_list_of_binding_lists( ListOfBindingLists, _ ) :-
    output('Yes - '~),
    for_in_list(
                    OneBindingList,
                    ListOfBindingLists,
                   (
                     output( spaces_(4) ),
                     write_binding_list( OneBindingList ),
                     output( nl_ )
                   )
               ).
 
 
write_binding_list( [] ) :-
    !.
write_binding_list( L ) :-
    write_binding_list_1( L ).
 
 
write_binding_list_1( [] ) :-
    !.
write_binding_list_1( [var(Var,Name)|T] ) :-
    write_binding_list_1( T ),
    (
        var(Var)
    ->
        output( Name...'is undefined  ' )
        /*  'output' has a bug-trap against writing unground variables:
            in any case, it's good to recognise here that the
            possibility exists. The student may, for example, have
            entered a fact with a capital instead of small letter
            starting an argument.
        */
    ;
        output( Name<>'='<>Var<>'  ' )
    ).
 
 
/*
Simulating the Prolog top-level interpreter.
--------------------------------------------
 
The main predicate is 'prolog_tli', which takes a goal and a
representation of its variable names, calls the goal, and displays the
result. If the goal contains at least one variable, 'prolog_tli' then
displays the 'More (y/n)' message and reads your answer to see whether
you want another solution. This is the way the TLI behaves on my system:
IT MAY BE DIFFERENT ON YOURS.
 
If your system allows you to call the TLI directly, then you can
dispense with most of this section.
 
'prolog_tli' calls 'tli_prolog_goal' to answer each goal, after setting the
current input and output streams.
 
'tli_prolog_goal' calls its goal, and then calls 'tli_display_answer'. This is a
bit messy. If the goal contains no variables, or only an anonymous
variable, 'tli_display_answer' succeeds without doing anything else, so
'tli_prolog_goal' just writes 'yes' and itself suceeds.
 
If the goal does contain variables, then 'tli_display_answer' displays the
values of all non-anonymous variables in the goal and then asks the user
whether he wants more solutions. It fails if the answer is 'yes', thus
causing 'tli_prolog_goal' to backtrack into the goal and try it again.
 
These predicates rely on the way that the variable names are represented,
of course.
 
Note that prolog_tli has a very limited repertoire. It does not, for
example, understand the [] notation for consult. This doesn't matter.
If you need the facilities of your sytem's own TLI, you can always do
    * prolog.
    * break?
inside the Tutor to invoke it. prolog_tli is quite adequate for what
the students need.    
*/
 
 
prolog_tli( Goal, Vars ) :-
    see(user), tell(user),
    tli_prolog_goal( Goal, Vars ),
    see(user), tell(user).
 
 
tli_prolog_goal( V, _ ) :-
    var(V),
    !,
    output( 'Goal is a variable'~ ).
 
tli_prolog_goal( X, Vars ) :-
    call( X ),
    tli_display_answer( Vars ),
    tell(user),
    output(yes~).
 
tli_prolog_goal( _, _ ) :-
    tell(user),
    output(no~).
 
 
/*  tli_display_answer( Vars+ ):
        If Vars is empty, succeed.
 
        Otherwise, write the variable bindings in it, and ask whether
        the user wants another answer. If so, _fail_; this will drive
        tli_prolog_goal to resatisfy the user's goal. Otherwise succeed.
*/
tli_display_answer( [] ) :- !.
 
tli_display_answer(Vars) :-
    tli_write_vars(Vars),
    ask_yn( 'More (y/n)? ', Reply ),
    ( Reply = yes -> output(nl_) ; true ),
    /*  This inserts an extra newline after the More question, to
        imitate what Poplog does.
    */
    Reply = no.
 
 
tli_write_vars( L ) :-
    for_in_list( E, L, tli_write_var(E) ).
 
 
tli_write_var( var(Var,Name) ) :-
    output( Name...'='...write_(Var)~ ).
 
 
:- endmodule.
