/*  COMMANDS.PL  */
 
 
:- module commands.
 
 
:- public
    obey_command/1,
    answer_question/1,
    add_fact/1,
    report_bad_command/2,
    report_bad_question/2,
    report_and_add_bad_fact/2,
    report_token_error/2,
    restore_auto_knowledge_base/0.
 
 
/*
SPECIFICATION
-------------
 
PUBLIC obey_command( Tree+ ):
PUBLIC answer_question( Tree+ ):
PUBLIC add_fact( Tree+ ):
PUBLIC report_bad_command( Tree+, Error+ ):
PUBLIC report_bad_question( Tree+, Error+ ):
PUBLIC report_and_add_bad_fact( Tree+ ):
 
This module exports the six predicates called by MAIN_LOOP.PL for
processing parsed sentences. All take parse trees as input. The first
three deal with the trees from syntactically correct commands,
questions, and facts. The others, which also take errors as arguments,
handle sentences in which the parser found syntax errors.
 
obey_command tries to obey the command, reporting any errors it finds
while doing so.
 
answer_question's action depends on which mode the Tutor is in. If in
logic mode, it generates all solutions to the question and displays them
one by one. If in prolog mode, it emulates a Prolog top-level
interpreter, asking after each solution whether it's to find the next
one.
 
add_fact inserts the fact at the specified place in the fact list and in
the Prolog database. If a fact is already in that position, it's
replaced.
 
report_bad_command and report_bad_question just report their error
arguments.
 
report_and_add_bad_fact reports the syntax error that was detected, and
adds the fact's tokens to the fact list for later editing.
 
 
restore_auto_knowledge_base:
 
If the file AUTO.LOGIC (the automatically-saved knowledge-base) exists,
outputs a message saying 'I am restoring your old facts', and reports on
whether it succeeded or not, showing the facts if it did. Called at
start-up.
*/
 
 
/*
IMPLEMENTATION
--------------
 
 
Commands.
---------
 
From outside this module, obey_command and report_bad_command appear to
be distinct predicates. However, it's useful to funnel them both through
the same process. This is because obey_command (which deals with
syntactically valid commands) is quite likely to detect execution errors
as it tries to obey a command. To present a consistent user interface,
these have to be reported in the same style as are syntax errors in
commands. For example, if a HELP file is displayed in the case of syntax
errors, it should also be displayed for execution errors. Thus it's
helpful to have the code for "obey command, possibly reporting execution
errors" next to the code for "report syntactically incorrect command".
 
For this reason, I define obey_command as
        process_command( Tree, ok )
and report_bad_command as
        process_command( Tree, Error )
where Error is the syntax error.
 
process_command looks up an appropriate predicate via
'command_predicate/4', and then calls the resulting goal. This ought to
be faster than a switch of the form
    process_command( command(again,_), Error ) :-
        do_again( Error ).
    process_command( command(again,Args), Error ) :-
        do_show_or_analyse( analyse, Args, for_display, Error ).
    ...
though I haven't timed it. Note that there are some sets of commands,
such as show/analyse and why/trace that are funnelled through the same
predicate.
 
 
Representing the internal state.
--------------------------------
 
Neglecting saved files, there are six components to the internal state
(see the specification in "The Logic Programming Tutor". It may be
helpful to know what these are and who they're handled by.
 
The Prolog database.
The previous question.
The editor's textbase.  - all handled by EDITOR.PL.
 
The sentence buffer.    - by MAIN_LOOP.PL.
 
Whether the Tutor is in Logic or Prolog mode.
                        - by assertions defined at the end of this
                          module.
 
Which script we have, and our current position within it.
                        - by SCRIPTS.PL.
 
Purists (*) would prefer them all to be passed as arguments. The sentence
buffer is; the database can't be; the others aren't because (a) I wasn't
a purist when I wrote the original; (b) all those extra arguments
clutter up the code; (c) it is easier to debug if you can interrupt the
Tutor and still see what the current state is.
 
Regarding interrupts: we simulate assignments to (for example) the mode
by doing
    retractall( is_mode(_) ),
    assert( is_mode(prolog) ).
It is a good idea to allow students to break-in (e.g. CONTROL-C on VMS),
aborting the current predicate, discarding whatever's in the sentence
buffer, and reentering the read-and-obey loop by calling
read_and_do_sentences afresh. I have not included code for this 'cos
there's no portable way to do it. Note though that if break-ins are
allowed, there is a chance that they could occur in the middle of a
state-change, e.g. just after the retractall above, this leaving the
state undefined. To circumvent this, you not only need break-ins, but
some way to protect some goal sequences from being interrupted. In
general, any retract(p(_))/assert(p(New)) pair should be thus protected.
 
(*) but they shouldn't worry. You _can_ give a respectable semantics to
programs that change their database; see Goguen and Meseguer (1987),
"Unifying Functional, Object-Oriented and Relational Programming with
Logical Semantics" in Research Directions in Object-Oriented Programming
edited by Bruce Shriver and Peter Wegner.
 
 
Error logging.
--------------
 
One experiment worth trying is error logging; that is, arrange that when
an error is reported, the sentence causing it and the diagnosis are
written to a log file. This is something I have not yet tried; it may
help in improving diagnostics, especially of faulty facts.
*/
 
 
:- needs
    again / 0,
    analyse_question / 1,
    answer / 2,
    append_fact / 2,
    assertion / 1,
    assertion / 3,
    bug / 2,
    cos_to_chars / 2,
    delete_fact / 3,
    delete_fact_strictly_matching / 2,
    delete_final_fact / 2,
    edit / 2,
    exists_auto_knowledge_base / 1,
    fact_vs_clause / 2,
    for_in_range / 4,
    insert_or_replace_fact / 3,
    is_good_fact / 1,
    is_good_question / 1,
    load_program / 2,
    next / 1,
    no_facts_stored / 0,
    no_question_stored / 0,
    number_to_fact / 2,
    open_scratch_output / 2,
    output / 1,
    positioned_fact_vs_position_and_fact / 3,
    print_and_delete_file / 2,
    question_vs_goal_and_vars / 3,
    range_is_all / 1,
    range_is_empty / 1,
    range_is_lu / 3,
    range_is_question / 1,
    range_type / 2,
    read_back / 2,
    restore_knowledge_base / 3,
    save_auto_knowledge_base / 0,
    save_knowledge_base / 2,
    script / 2,
    section / 2,
    show_fact / 2,
    show_question / 2,
    store_question / 1,
    string_edit_fact / 5,
    string_edit_question / 5,
    sync_database / 0,
    the_question / 1,
    trace_question / 2,
    why_question / 2.
 
 
dynamic
    error_context / 1,
    saved_explanation/1,
    have_edited/1,
    changes/1.
 
 
obey_command( Tree ) :-
    process_command( Tree, ok ).
 
 
report_bad_command( Tree, Error ) :-
    process_command( Tree, Error ).
 
 
process_command( command( C, Aargs ), E ) :-
    command_predicate( C, Aargs, E, P ),
    call( P ).
 
 
command_predicate( again,   A, E, do_again(E) ) :- !.
command_predicate( analyse, A, E, do_show_or_analyse(analyse,A,for_display,E) ) :- !.
command_predicate( append,  A, E, do_get(append,A,E) ) :- !.
command_predicate( ask,     A, E, do_ask(E) ) :- !.
command_predicate( bye,     A, E, do_bye(E) ) :- !.
command_predicate( change,  A, E, do_change(A,E) ) :- !.
command_predicate( copy,    A, E, do_copy(A,E) ) :- !.
command_predicate( delete,  A, E, do_delete(A,E) ) :- !.
command_predicate( edit,    A, E, do_edit(A,E) ) :- !.
command_predicate( explain, A, E, do_explain(E) ) :- !.
command_predicate( load,    A, E, do_get(load,A,E) ) :- !.
command_predicate( logic,   A, E, do_mode(logic,E) ) :- !.
command_predicate( next,    A, E, do_next(E) ) :- !.
command_predicate( prolog,  A, E, do_mode(prolog,E) ) :- !.
command_predicate( restore, A, E, do_get(restore,A,E) ) :- !.
command_predicate( save,    A, E, do_save(A,E) ) :- !.
command_predicate( script,  A, E, do_script(A,E) ) :- !.
command_predicate( section, A, E, do_section(A,E) ) :- !.
command_predicate( show,    A, E, do_show_or_analyse(show,A,for_display,E) ) :- !.
command_predicate( trace,   A, E, do_trace_or_why(trace,A,E) ) :- !.
command_predicate( why,     A, E, do_trace_or_why(why,A,E) ) :- !.
 
 
 
/*  AGAIN  */
 
do_again( ok ) :-
    !,
    again.
 
do_again( Error ) :-
    report_and_saynotdone( Error, 'repeated the previous section.'~ ).
 
 
/*  ASK  */
 
do_ask( ok ) :-
    range_is_question( Range ),
    no_question_stored,
    !,
    output( 'There is no previous question to answer.'~ ).
 
do_ask( ok ) :-
    range_is_question( Range ),
    the_question(Q),
    not( is_good_question(Q) ),
    !,
    output(
'The previous question had an error, so I haven\'t answered it.'~
          ).
 
do_ask( ok ) :-
    !,
    the_question(Q),
    output( 'Your question is this:'~ ),
    show_question( Q, [wrap_bad,question_mark_and_nl] ),
    output( 'I shall now answer it:'~ ),
    answer_question(Q).
 
do_ask( Error ) :-
    report_and_saynotdone( Error, 'answered your last question.'~ ).
 
 
/*  BYE  */
 
do_bye( ok ) :-
    !,
    output( 'I am saving your facts.'~ ),
    save_auto_knowledge_base,
    output( 'BYE FOR NOW'~ ),
    halt.
 
do_bye( Error ) :-
    report_and_saynotdone( Error, 'left Logic.'~ ).
 
 
/*  CHANGE  */
 
do_change( change(_,_,Range), ok ) :-
    range_type( Range, facts ),
    no_facts_stored,
    !,
    output('There are no facts stored at all, so I haven\'t changed any.'~ ).
 
do_change( change(_,_,Range), ok ) :-
    range_type( Range, facts ),
    range_is_empty( Range ),
    !,
    output(
     'There are no facts within this range, so I haven\'t changed any.'~ ).
 
do_change( change(_,_,Range), ok ) :-
    range_type( Range, question ),
    no_question_stored,
    !,
    output('There is no previous question, so I haven\'t changed it.'~ ).
 
do_change( change(S,S,_), ok ) :-
    !,
    output('You\'re trying to replace a string by itself!'~<>
           'That will have no effect, so I haven\'t done anything.'~
    ).
 
do_change( change('',_,_), ok ) :-
    !,
    output('You are not allowed to replace nothing!'~ ).
 
do_change( change(S1,S2,Range), ok ) :-
    range_type( Range, facts ),
    !,
    for_in_range( FactNumber, _,
                  Range,
                  string_edit_fact_numbered(FactNumber,S1,S2)
                ).
 
do_change( change(S1,S2,Range), ok ) :-
    range_type( Range, question ),
    !,
    the_question( Q ),
    string_edit_question( Q, S1, S2 ).
 
do_change( _, Error ) :-
    report_and_saynotdone( Error, 'made any changes.'~ ).
 
 
string_edit_fact_numbered( OldFactNumber, S1, S2 ) :-
    number_to_fact( OldFactNumber, OldFact ),
    string_edit_fact( OldFact, S1, S2, NewFact, Status ),
    (
        Status = no_change
    ->
        output('I can\'t find the string "'<>S1<>'" in fact'...
               OldFactNumber<>', so haven\'t changed it.'~ )
    ;
        put_back_new_fact( OldFactNumber, OldFact, NewFact, Status ),
        note_another_change( replace, OldFactNumber ),
        range_is_lu( Range, OldFactNumber, OldFactNumber ),
        show_range( Range, for_display )
    ).
 
 
put_back_new_fact( FactNumber, OldFact, NewFact, NewFactStatus ) :-
    is_good_fact( NewFact ),
    !,
    insert_or_replace_fact( FactNumber, NewFact, _ ),
    (
        is_good_fact(OldFact)
    ->
        output('OK - fact'...FactNumber...'replaced.'~ )
    ;
        output('OK - fact'...FactNumber...'replaced.'...
               'It\'s now grammatically correct.'~)
    ).
 
put_back_new_fact( FactNumber, OldFact, NewFact, NewFactStatus ) :-
    insert_or_replace_fact( FactNumber, NewFact, _ ),
    output('I have replaced fact'...FactNumber<>','~<>
           'but you must change it again'...
           'to make it grammatically correct.'~
    ),
    report_diagnosis( NewFact, NewFactStatus ).
 
 
string_edit_question( Q, S1, S2 ) :-
    string_edit_question( Q, S1, S2, NewQ, NewQStatus ),
    (
        NewQStatus = no_change
    ->
        output('I can\'t find the string "'<>S1<>'" in the question,'<>
               'so haven\'t changed it.'~ )
    ;
        output( 'Your new question is this:'~ ),
        show_question( NewQ, [wrap_bad,question_mark_and_nl] ),
        (
            NewQStatus = ok
        ->
            output( 'I shall now answer it:'~ ),
            answer_question( NewQ )
            /*  That also stores the question.  */
        ;
            output( 'There is an error in the new question:'~ ),
            report_diagnosis( NewQ, NewQStatus ),
            store_question( NewQ )
        )
    ).
 
 
/*  COPY  */
 
do_copy( from_to(From,To), ok ) :-
    !,
    (
        number_to_fact( From, Fact )
    ->
        (
            To = end
        ->
            append_fact( Fact, NewFactNumber ),
            output('OK - fact'...From...'copied to'...NewFactNumber<>'.'~),
            report_if_bad_copy( Fact, NewFactNumber ),
            note_another_change( add, NewFactNumber )
        ;
            (
                insert_or_replace_fact( To, Fact, inserted )
            ->
                output('OK - fact'...From...'copied to'...To<>'.'~),
                report_if_bad_copy( Fact, To ),
                note_another_change( add, To )
            ;
                output( 'Fact'...To...
                        'was already there, so I haven\'t copied over it.'~)
            )
        )
    ;
        output('Fact'...From...'doesn\'t exist, so I haven\'t copied it.'~)
    ).
 
do_copy( _, Error ) :-
    report_and_saynotdone( Error, 'copied your fact.'~ ).
 
 
report_if_bad_copy( Fact, NewNo ) :-
    not( is_good_fact(Fact) ),
    !,
    output( 'By the way, the fact you have copied contained an error.'~<>
            'But I have copied it anyway.'~
          ).
 
report_if_bad_copy( _, _ ).
 
 
/*  DELETE  */
 
do_delete( _, ok ) :-
    no_facts_stored,
    !,
    output( 'There are no facts stored at all, so I haven\'t deleted any.'~ ).
 
do_delete( Fact, ok ) :-
    fact_vs_clause( Fact, Clause ),
    !,
    (
        delete_fact_strictly_matching( FactNumber, Clause )
    ->
        output('OK - fact'...FactNumber...'deleted.'~),
        note_another_change( delete, FactNumber )
    ;
        output('I can\'t find this fact so haven\'t deleted it.'~)
    ).
 
do_delete( empty, ok ) :-
    !,
    delete_final_fact( FactNumber, DeletedFact ),
    output( 'I assume you mean delete:'~ ),
    show_fact( DeletedFact, [number(N),wrap_bad,dot_and_nl] ),
    output( 'OK - fact'...FactNumber...'deleted.'~ ),
    note_another_change( delete, FactNumber ).
 
do_delete( Range, ok ) :-
    range_is_empty( Range ),
    output( 'There are no facts within this range'...
            'so I haven\'t deleted any.'~ ),
    !.
 
do_delete( Range, ok ) :-
    !,
    for_in_range( FactNumber, _,
                  Range,
                  delete_fact_numbered(FactNumber)
                ).
 
do_delete( _, Error ) :-
    report_and_saynotdone( Error, 'deleted any facts.'~ ).
 
 
delete_fact_numbered( N ) :-
    delete_fact( N, _, _ ),
    output('OK - fact'...N...'deleted.'~),
    note_another_change( delete, N ).
 
 
/*  EDIT  */
 
do_edit( Range, ok ) :-
    !,
    range_to_chars( Range, Chars ),
    edit( Chars, NewChars ),
    (
        NewChars = quit
    ->
        output('OK - I will leave everything as it was.'~)
    ;
        output('OK - I will now process the new sentences.'~),
        read_back( NewChars, Status ),
        (
            Status = ok
        ->
            true
        ;
            assertion( Status=token_error(Message,Input),
                       'do_edit: Status wrong', [Status]
                     ),
            report_and_saynotdone( edit+Status, 'changed your facts.'~ )
        )
    ).
 
do_edit( _, Error ) :-
    report_and_saynotdone( Error, 'edited any facts.'~ ).
 
 
/*  range_to_chars( Range+, Chars- ):
        Unifies Chars with a character list containing the text of the
        facts or question in Range. Used to generate text for
        screen-editing.
*/
range_to_chars( Range, Chars ) :-
    cos_to_chars( show_range(Range,for_edit), Chars ).
 
 
/*  EXPLAIN  */
 
do_explain( ok ) :-
    saved_explanation( E ),
    output( E ).
 
do_explain( ok ) :-
    error_context(_),
    !,
    /*  not(saved_explanation(_)), error_context(_)  */
    output( 'I have not been given an explanation of this.'~ ).
 
do_explain( ok ) :-
    !,
    /*  not(saved_explanation(_)), not(error_context(_))  */
    output( 'There is no error to explain.'~ ).
 
do_explain( Error ) :-
    report_and_saynotdone( Error, 'given your explanation.'~ ).
 
 
/*  GET  */
 
do_get( load, Filename, ok ) :-
    !,
    load_program( Filename, Status ),
    (
        Status = ok
    ->
        output('OK - facts loaded from'...Filename<>'.'~)
    ;
        report_and_saynotdone( load+Status, 'loaded the knowledge base.'~ )
    ).
 
do_get( Get, Filename, ok ) :-
    !,
    output('This may take a little time...'~),
    ( Get = append -> G = appended ; G = restored ),
    restore_knowledge_base( Get, Filename, Status ),
    (
        Status = ok
    ->
        output('OK - facts'...G...'from'...Filename<>'.'~),
        ( Get = restore -> retractall(have_edited(_)) ; true ),
        range_is_all( All ),
        show_range( All, for_display )
    ;
        report_and_saynotdone( Get+Status, G...'the knowledge base.'~ )
    ).
 
do_get( Get, Filename, Error ) :-
    report_and_saynotdone( Error, 'recovered your knowledge base.'~ ).
 
 
/*  MODE  */
 
do_mode( Mode, ok ) :-
    (
        is_mode( Mode )
    ->
        output('OK - mode stays at'...Mode<>'.'~)
    ;
        retractall( is_mode(_) ),
        asserta( is_mode(Mode) ),
        output('OK - mode changed to'...Mode<>'.'~)
    ).
 
do_mode( Mode, Error ) :-
    report_and_saynotdone( Error, 'changed the mode.'~ ).
 
 
/*  NEXT  */
 
do_next( ok ) :-
    next( Status ),
    (
        Status = eof_script
    ->
        output( 'You were already on the final section,'...
                'so I can\'t display the next one.'~ )
    ;
        true
    ).
 
do_next( Error ) :-
    report_and_saynotdone( Error, 'moved to the next section.'~ ).
 
 
/*  SAVE  */
 
do_save( Filename, ok ) :-
    !,
    save_knowledge_base( Filename, Status ),
    (
        Status = ok
    ->
        output('OK - knowledge base saved in'...Filename<>'.'~)
    ;
        report_and_saynotdone( save+Status, 'saved the knowledge base.'~ )
    ).
 
do_save( Filename, Error ) :-
    report_and_saynotdone( Error, 'saved your knowledge base.'~ ).
 
 
/*  SCRIPT  */
 
do_script( Name, ok ) :-
    !,
    script( Name, Status ),
    (
        Status \= ok
    ->
        report_and_saynotdone( script+Status,'changed to a new script.'~)
    ;
        true
    ).
 
 
do_script( _, Error ) :-
    report_and_saynotdone( Error, 'changed to a new script.'~ ).
 
 
/*  SECTION  */
 
do_section( N, ok ) :-
    !,
    section( N, Status ),
    (
        Status = no_section_found
    ->
        report_and_saynotdone( section+Status/N, 'changed to a new section.'~ )
    ;
        true
    ).
 
do_section( _, Error ) :-
    report_and_saynotdone( Error, 'changed to a new section.'~ ).
 
 
/*  SHOW OR ANALYSE  */
 
do_show_or_analyse( Which, show(Where,Range), Verbosity, ok ) :-
    range_is_question( Range ),
    no_question_stored,
    !,
    (
        Verbosity = for_display
    ->
        output( 'There is no previous question to'...Which<>'.'~ )
    ;
        output( ''~ )
    ).
 
do_show_or_analyse( Which, show(Where,Range), Verbosity, ok ) :-
    not( range_is_question(Range) ),
    no_facts_stored,
    !,
    (
        Verbosity = for_display
    ->
        output( 'There are no facts stored at all.'~ )
    ;
        output( ''~ )
    ).
 
do_show_or_analyse( Which, show(Where,Range), Verbosity, ok ) :-
    not( range_is_question(Range) ),
    range_is_empty( Range ),
    !,
    (
        Verbosity = for_display
    ->
        output( 'There are no facts within this range.'~ )
    ;
        output( ''~ )
    ).
 
do_show_or_analyse( Which, show(Where,Range), Verbosity, ok ) :-
    !,
    (
        Where \= screen
    ->
        telling( COS ),
        open_scratch_output( PrintFile, _ )
    ;
        true
    ),
    (
        range_is_question( Range )
    ->
        /*  It contains a question: otherwise trapped above.  */
        the_question(Q),
        (
            Which = show
        ->
            (
                Verbosity = for_display
            ->
                Options = [wrap_bad,question_mark_and_nl]
            ;
                Options = [question_mark_and_nl]
            ),
            show_question( Q, Options )
        ;
            analyse_question(Q)
        )
    ;
        /*  It contains some facts: otherwise trapped above.  */
        ( Which = show ->
            (
                Verbosity = for_display
            ->
                Options = [wrap_bad,dot_and_nl,number(FactNumber)]
            ;
                Options = [dot_and_nl,number(FactNumber)]
            ),
            for_in_range( FactNumber, Fact, Range, show_fact(Fact,Options) )
        ;
            for_in_range( FactNumber, Fact, Range, analyse_fact(FactNumber,Fact) )
        )
    ),
    (
        Where \= screen
    ->
        told,
        tell( COS ),
        logic_name_vs_vms_name( Where, VMSNameForPrinter ),
        print_and_delete_file( PrintFile, VMSNameForPrinter ),
        ( range_is_question(Range) -> Object = question ; Object = facts ),
        output( 'OK - I have told'...
                Where<>'\'s printer to print your'...Object<>'.'~
              )
    ;
        true
    ).
 
do_show_or_analyse( Which, _, _, Error ) :-
    report_and_saynotdone( Error, 'done your listing.'~ ).
 
 
/*  TRACE OR WHY  */
 
do_trace_or_why( Which, Where, ok ) :-
    range_is_question( Range ),
    no_question_stored,
    !,
    ( Which = trace -> Verb = trace ; Verb = explain ),
    output( 'There is no previous question to'...Verb<>'.'~ ).
 
do_trace_or_why( Which, Where, ok ) :-
    range_is_question( Range ),
    the_question(Q),
    not( is_good_question(Q) ),
    !,
    ( Which = trace -> Verb = traced ; Verb = justified ),
    output(
'The previous question had an error, so I haven\'t'...Verb...'it.'~
          ).
 
do_trace_or_why( Which, Where, ok ) :-
    !,
    (
        Where \= screen
    ->
        telling( COS ),
        open_scratch_output( PrintFile, _ )
    ;
        true
    ),
    the_question(Q),
    output( 'Your question was this:'~ ),
    show_question( Q, [wrap_bad,question_mark_and_nl] ),
    (
        Which = trace
    ->
        output( 'I shall now trace it:'~ )
    ;
        output( 'I shall now explain the answer:'~ )
    ),
    sync_database,
    ( Which = trace -> trace_question(Q,Where) ; why_question(Q,Where) ),
    (
        Where \= screen
    ->
        told,
        tell( COS ),
        logic_name_vs_vms_name( Where, VMSNameForPrinter ),
        print_and_delete_file( PrintFile, VMSNameForPrinter ),
        ( Which = trace -> Object = trace ; Object = justification ),
        output( 'OK - I have told'...
                Where<>'\'s printer to print your'...Object<>'.'~
              )
    ;
        true
    ).
 
do_trace_or_why( Which, _, Error ) :-
    ( Which = trace -> Verb = traced
    ; Verb = justified
    ),
    report_and_saynotdone( Error, Verb...'the last question.'~ ).
 
 
/*
Answering questions
-------------------
 
To answer a syntactically correct question, we first call
'sync_database'. This ensures that the Prolog database is in step with
the editor's dictionary (see EDITOR.PL), i.e. that all correct facts
have actually been asserted. We also extract the Prolog goals comprising
it, and a list of the variable names it contains (we need this when
displaying the values that variables get bound to). We then pass these
to 'answer', which does the work.
 
For incorrect questions, we just report the error.
 
Both predicates store the question.
*/
 
 
answer_question( Question ) :-
    store_question( Question ),
    question_vs_goal_and_vars( Question, Goal, Vars ),
    !,
    sync_database,
    answer( Goal, Vars ).
 
answer_question( Question ) :-
    bug( 'do_question: bad argument', Question ).
 
 
report_bad_question( Question, Error ) :-
    output( 'There is an error in your question.'~ ),
    store_question( Question ),
    report_diagnosis( Question, Error ).
 
 
/*
Adding facts
------------
 
Both add_fact and report_and_add_bad_fact work in the same way. If the
fact is to be appended to the end of the database, we call
'append_fact'. Otherwise, we call 'insert_or_replace_fact'. This may
delete a fact that's already there, as adding
    10 john loves mary
would delete
    10 jason loves miranda
 
insert_or_replace_fact returns in its ChangeType argument something to
indicate whether an old fact has been replaced. This is used in the
confirmatory message that follows.
*/
 
 
add_fact( PositionedFact ) :-
    positioned_fact_vs_position_and_fact( PositionedFact, P, Fact ),
    (
        P = end
    ->
        append_fact( Fact, NewFactNumber ),
        output('OK - fact'...NewFactNumber...'recorded.'~),
        note_another_change( add, NewFactNumber )
    ;
        insert_or_replace_fact( P, Fact, ChangeType ),
        ( ChangeType = replaced(_) -> Verb = replaced ; Verb = inserted ),
        output( 'OK - fact'...P...Verb<>'.'~ ),
        note_another_change( replace, P )
    ).
 
 
report_and_add_bad_fact( PositionedFact, Error ) :-
    positioned_fact_vs_position_and_fact( PositionedFact, P, Fact ),
    (
        P = end
    ->
        append_fact( Fact, FactNumber ),
        output('I have recorded this as fact number'...FactNumber<>','~<>
               'but it is not a grammatically correct fact.'~
              )
    ;
        FactNumber = P,
        insert_or_replace_fact( FactNumber, Fact, ChangeType ),
        ( ChangeType = replaced(_) -> Verb = replaced ; Verb = inserted ),
        output('I have'...Verb...'your new fact as number'...FactNumber<>','~<>
               'but you must change it again to make it grammatically correct.'~
        )
    ),
    report_diagnosis( Fact, Error ),
    note_another_change( add, FactNumber ).
    /*  Since 'note_another_change' may provoke a 'saving your facts'
        message, put it _after_ the diagnostics.
    */
 
 
/*
Reporting tokenisation errors.
------------------------------
*/
 
 
 
/*
Restoring the automatically saved knowledge base.
-------------------------------------------------
*/
 
 
restore_auto_knowledge_base :-
    exists_auto_knowledge_base( Auto ),
    !,
    output( 'I am restoring your old facts...'~ ),
    restore_knowledge_base( restore, Auto, Status ),
    (
        Status = ok
    ->
        (
            no_facts_stored
        ->
            output('I tried restoring them,'...
                   'but there weren\'t any.'~ )
        ;
            output('I have now restored them. Here they are:'~ ),
            show_range( all, for_display )
        )
    ;
        report_and_saynotdone( Status,
                               'restored the facts.'~ )
    ).
 
restore_auto_knowledge_base.
 
 
/*
Printing
--------
*/
 
 
logic_name_vs_vms_name( ctc, 'CTC' ).
logic_name_vs_vms_name( ep, 'PSYZOO' ).
logic_name_vs_vms_name( oucs, 'USERAREA' ).
logic_name_vs_vms_name( ss, 'SOCSTUD' ).
 
 
/*
Reporting
---------
*/
 
 
/*  report_and_saynotdone( Command+Error +, Action+ ):
        Report an error in Command, and say that therefore Action has
        not been performed. Action is a message; Error is an error code.
 
        Then save the further help on Error as an explanation for
        outputting by the "explain" command. If Commmand is itself
        "explain", we _don't_ do this, since we don't want to overwrite
        the explanation that the user is trying to get.
*/
report_and_saynotdone( Command+Error, Action ) :-
    retractall( error_context(_) ),
    asserta( error_context(Command+Error) ),
    report_and_saynotdone_1( Command+Error, Action ).
 
 
report_and_saynotdone_1( explain+Error, Action ) :-
    !,
    assertion( command_help( explain, Error, Text, FurtherHelp ) ),
    output( Text<>
            'Because of that error, I haven\'t'...Action ).
 
report_and_saynotdone_1( Command+Error, Action ) :-
    assertion( command_help( Command, Error, Text, FurtherHelp ) ),
    output( Text<>
            'Because of that error, I haven\'t'...Action ),
    save_explanation( FurtherHelp ).
 
 
/*  report_diagnosis( FaultyObject+, Diagnosis+ ):
        Report an error in a non-command object, i.e. a fact or
        question. The error may be quite specific, hence deserving to be
        called a diagnosis, if it concerns something wrong with a Logic
        fact (see PARSE_LOGIC.PL).
 
        Then save the help text and further explanation for outputting
        by the "explain" command.
*/
report_diagnosis( Origin, Diagnosis ) :-
    retractall( error_context(_) ),
    asserta( error_context(Origin+Diagnosis) ),
    report_diagnosis_1( Origin, Diagnosis ).
 
 
report_diagnosis_1( Origin, Diagnosis ) :-
    assertion( help_text( Diagnosis, HelpText, FurtherHelp ),
               'report_diagnosis: no help text', [Origin,Diagnosis]
             ),
    !,
    save_explanation( HelpText~<>FurtherHelp~ ).
 
 
/*  save_explanation( Text+ ):
        Text is the text of an explanation. Save it for outputting by
        the "explain" command.
*/
save_explanation( Text ) :-
    retractall( saved_explanation(_) ),
    asserta( saved_explanation(Text) ).
 
 
/*
Utilities
---------
*/
 
 
note_another_change( Type, FactNumber ) :-
    retractall( have_edited(_) ),
    (
        ( Type = add ; Type = replace )
    ->
        assert( have_edited(FactNumber) )
    ;
        true
    ),
    ( retract( changes(N) ) ; N = 0 ),
    !,
    (
        N > 5
    ->
        output( 'I am saving your facts.'~ ),
        save_auto_knowledge_base,
        asserta( changes(0) )
    ;
        Ni is N + 1,
        asserta( changes(Ni) )
    ).
 
 
/*  show_range( Range+, Verbosity+ ):
        Utility predicate used here for displaying to screen the
        contents of Range.
 
        Verbosity in { for_edit, for_display }. This controls the form
        of the 'range empty' message. Use 'for_edit' if writing the
        message into the edit window (with range_to_chars), otherwise
        'for_display'. It also controls whether incorrect facts are
        listed with surrounding chevrons - for editing, they are not.
*/
show_range( Range, Verbosity ) :-
    do_show_or_analyse( show, show(screen,Range), Verbosity, _ ).
