/*  KBFILES.PL  */
 
 
:- module kbfiles.
 
 
:- public save_knowledge_base/2,
          restore_knowledge_base/3,
          save_auto_knowledge_base/0,
          exists_auto_knowledge_base/1,
          load_program/2.
 
 
/*
SPECIFICATION
-------------
 
This module defines the predicates that save the fact dictionary to
file, and restore it from file. I have chosen to save facts in a form
that's as similar to their Prolog translation as possible - this enables
teachers and students to convert Tutor fact files to Prolog, or vice
versa, with as little trouble as possible. The representation of facts
on file is therefore part of the specification of this module, rather
than just an implementation detail.
 
There are two kinds of fact: syntactically correct and syntactically
incorrect. The former are written out as their Prolog clauses, and
pretty-printed to make them easy to read. The latter do not make sense
as terms on their own (if they did, they wouldn't _be_ incorrect!). To
make them into terms, we write out their characters as a Prolog term, and
wrap them in the functor '$text'. Thus, if a fact file is consulted into
Prolog, the syntactically incorrect terms will not interfere with
anything else (except in the unlikely event that your program uses
clauses for '$text'/1).
 
Thus, on reading facts from a file, the following structures are
recognised:
    TERM            EFFECT
    :- G.           call(G) - continue regardless of failure.
    ?- G.           call(G) - continue regardless of failure.
    '$text'(T)      Treat T as the tokens making up a syntactically
                    incorrect fact, and append it.
    Any other term  Append it as a syntactically good fact.
 
Directives :-G and ?-G won't (unless I change it since writing this) be
output by the Tutor. But it does no harm to handle them; they might
appear in fact files that started life as stand-alone Prolog programs.
 
Each fact is preceded by a comment giving the number it had in the
Tutor. These comments are not used on reading back - facts are allocated
numbers upwards at intervals of 10.
 
 
 
PUBLIC save_knowledge_base( KB+, Status- ):
 
Write the current knowledge base to the file named KB in such a way that
it can be read back by restore_knowledge_base. The default extension for
KB is .LOGIC.
 
Status as for open_output: {no_space_for_file, directory_not_found,
access_forbidden, invalid_filename, ok}.
 
 
PUBLIC restore_knowledge_base( Get+, KB+, Status- ):
 
Read a saved knowledge base from the file named KB. Get is one of {
restore, append }. If it's restore, then delete the existing knowledge
base first; if it's append, then append the new one to the end. The
default extension for KB is .LOGIC; look first in the current directory,
and then in LOGIC$LIB:. Status = one of {file_not_found,
directory_not_found, access_forbidden, invalid_filename, ok}.
 
 
PUBLIC save_auto_knowledge_base:
 
Saves the facts to the file AUTO.LOGIC in the current directory.
 
 
PUBLIC exists_auto_knowledge_base( Name- ):
 
If AUTO.LOGIC exists, assign its name to Name. This is exported just
to avoid bringing details of the file names outside this module.
 
 
PUBLIC load_program( Prog+, Status- ):
 
Reconsult clauses from the file named Prog. The default extension for
Prog is .PL; look first in the current directory, and then in
LOGIC$LIB:. Status = {file_not_found, directory_not_found,
access_forbidden, invalid_filename, ok}.
 
 
Portability note: you may have to change the directory- and extension-
defining clauses. See below.
*/
 
 
/*
IMPLEMENTATION
--------------
 
 
Writing fact files
------------------
 
This is a matter of opening the file and applying the predicate
save_fact, defined below, to each fact in the editor's dictionary.
save_fact calls write_clause/4 to pretty-print a clause to the COS.
 
Files can only be written to the current directory. Its name is defined
by the assertion
    current_directory_is( CurrentDirectory ).
 
 
Reading fact files
------------------
 
There are two classes of fact file. Some are written by the teacher for
the student to load and use in exercises. Others are the result of the
student saving his or her own work. At Oxford, the teacher's files live
in a standard directory separate from those of the students. This makes
them safe from tampering, and also obviates the need to make copies one
per student.
 
We take care of this by using the find_file mechanism. The clause below
for
    readable_knowledge_base_directories_are/1
defines a list of directories to be searched, in order. The current
directory is first in this list, followed by the VMS logical name
LOGIC$LIB, which points at the teacher's fact files. The default
extension for fact files is .LOGIC, defined by
knowledge_base_extension_is/1.
 
Terms are read by cis_to_facts, which calls append_saved_term to process
individual terms. There is one possible non-portability here. We need a
version of 'read' that can get hold of the names variables had in the
term. Your system may supply this as read/2. If it doesn't, you can use
the one supplied in READ_TERM.PL, and exported as "vread/2". Note that
if you do use your own read/2, it may return variable lists in a
different form. To avoid propagating such changes throughout the rest of
the Tutor (variables can also come in from the student's interactive
input) the best thing is to define
    vread( Term, Vars ) :-
        read( Term, MyVars ),
        convert_vars( MyVars, Vars )
where convert_vars produces a variable-name list in the same form as
vread assumes.
 
 
Loading programs
----------------
 
The student can also use the "load" command to load from a normal Prolog
file. This won't appear in the facts dictionary, and we just do a
reconsult, inside load_program/2. As with fact files, "load" looks first
in the current directory, and then in LOGIC$LIB; these are defined by
readable_program_directories_are/1. The default extension for program
files is .PL, defined by program_extension_is/1.
*/
 
 
:- needs
    add_file_defaults / 2,
    append_fact / 2,
    call_without_failure / 1,
    fact_vs_clause_and_vars / 3,
    fact_vs_text / 2,
    find_file / 5,
    for_all_facts / 3,
    for_in_range / 4,
    is_good_fact / 1,
    open_and_reconsult / 2,
    open_input / 2,
    open_output / 2,
    output / 1,
    range_is_all / 1,
    show_fact / 3,
    while / 2.
 
 
knowledge_base_extension_is( '.logic' ).
 
readable_knowledge_base_directories_are( [ '',
                                           'logic$lib:',
                                           '[popx.book.disc.traveller]',
                                           '[popx.book.disc.kbs]'
                                         ]
                                       ).
 
current_directory_is( [ '' ] ).
 
auto_knowledge_base_is( 'auto' ).
 
program_extension_is( '.pl' ).
 
readable_program_directories_are( [ '',
                                    'logic$lib:',
                                    '[popx.book.disc.traveller]',
                                    '[popx.book.disc.kbs]'
                                  ]
                                ).
 
 
save_knowledge_base( KB, StatusOut ) :-
    knowledge_base_extension_is( Ext ),
    current_directory_is( Dir ),
    add_file_defaults( KB+defaults(Dir,Ext), Fullname ),
    telling( COS ),
    open_output( Fullname, Status ),
    (
        Status = ok
    ->
        facts_to_cos,
        told
    ;
        true
    ),
    tell( COS ),
    StatusOut = Status.
 
 
restore_knowledge_base( Get, KB, StatusOut ) :-
    seeing( CIS ),
    knowledge_base_extension_is( Ext ),
    readable_knowledge_base_directories_are( Dirs ),
    find_file( KB, Dirs, Ext, Fullname, Status1 ),
    (
        Status1 = ok
    ->
        open_input( Fullname, Status ),
        (
            Status = ok
        ->
            (
                Get = restore
            ->
                range_is_all( All ),
                for_in_range( FactNumber, _,
                    All,
                    delete_fact_numbered(FactNumber)
                )
            ;
                true
            ),
            cis_to_facts
        ;
            true
        ),
        seen, see( CIS )
    ;
        Status = Status1
    ),
    StatusOut = Status.
 
 
load_program( Prog, StatusOut ) :-
    program_extension_is( Ext ),
    readable_program_directories_are( Dirs ),
    find_file( Prog, Dirs, Ext, Fullname, Status ),
    (
        Status = ok
    ->
        open_and_reconsult( Fullname, Status )
    ;
        true
    ),
    StatusOut = Status.
 
 
save_auto_knowledge_base :-
    auto_knowledge_base_is( Auto ),
    save_knowledge_base( Auto, _ ).
 
 
exists_auto_knowledge_base( Auto ) :-
    auto_knowledge_base_is( Auto ),
    knowledge_base_extension_is( Ext ),
    current_directory_is( Dir ),
    find_file( Auto, [Dir], Ext, _, ok ).
    /*  I.e. it exists if find_file( ..., Status ) returns a Status
        of ok, when looking only in the current directory.
    */
 
 
/*  facts_to_cos:
        Writes all facts in the dictionary to the COS.
*/
facts_to_cos :-
    for_all_facts(
                    Number, Fact,
                    save_fact( Number, Fact )
                 ).
 
 
/*  save_fact( Number+, Fact+ ):
        Writes a single fact to the COS.
*/
save_fact( Number, Fact ) :-
    is_good_fact( Fact ),
    !,
    output( nl_<>nl_<>'/*'...Number...'*/'~ ),
    fact_vs_clause_and_vars( Fact, Clause, Vars ),
    show_fact( Fact, prolog, [dot_and_nl] ).
 
save_fact( Number, Fact ) :-
    fact_vs_text( Fact, Text ),
    output( nl_<>nl_<>'/*'...Number...'*/'~ ),
    writeq( '$text'(Text) ), write('.'), nl, nl.
 
 
/*  cis_to_facts:
        Reads terms from the CIS until end-of-file, and, if they're
        facts, appends them to the editor's dictionary. See the
        implementation comments above for details of the mapping.
 
        Note (as also mentioned there) that you should replace
        vread/2 by read/2 if your system has it (but take care
        with variable name formats if you do).
 
*/
cis_to_facts :-
    while(
           ( vread( X ,Vars ),
             X\==end_of_file
           )
         ,
           (
             append_saved_term( X, Vars )
           )
         ).
 
 
/*  append_saved_term( Term+, Vars+ ):
        Decides what to do with a term. Vars is Term's variable
        dictionary, and is ignored except for syntactically good facts.
*/
append_saved_term( (?-Y), _ ) :-
    !,
    call_without_failure(Y).
 
append_saved_term( (:-Y), _ ) :-
    !,
    call_without_failure(Y).
 
append_saved_term( '$text'(T), _ ) :-
    !,
    fact_vs_text( F, T ),
    append_fact( F, _ ).
 
append_saved_term( Term, Vars ) :-
    fact_vs_clause_and_vars( F, Term, Vars ),
    append_fact( F, _ ).
 
 
:- endmodule.
