/*  PARSE.PL  */
 
 
:- module parse.
 
 
:- public parse/4,
          parse_fact/3,
          parse_question/3,                 
          is_positioned_fact/1,
          is_command/1,
          positioned_fact_vs_position_and_fact/3,
          command_vs_commandname/2.
 
 
/*
SPECIFICATION
-------------
 
This module exports parse/4, the predicate for parsing input to the
Tutor, and subsidiary predicates (used in editing) for parsing sentences
known already to be facts or questions. It also exports selector
predicates for the trees generated by parse.
 
There are three types of sentence: positioned facts, questions, and
commands. The trees generated by parsing them have the following
components:
 
1) Positioned facts - position, fact
 
The position indicates where the fact is to be inserted in the textbase.
It is either an integer, if the fact was something like
    30 ron has_pet python
or the atom 'end' if the fact did not start with a number:
    nick has_pet cat.
 
'end' indicates the fact should be added after all the others.
 
The fact is the representation of the fact itself - either the
translation into Prolog, or a list of tokens. Selectors for facts - the
clause, goals, variable dictionary, token list, etc. - are defined in
EDITOR.PL.
 
2) Questions - the question.
 
Questions don't have a position, since only one question is ever stored.
Consequently, the only component to the representation is the
translation or tokens, also defined in EDITOR.PL.
 
3) Commands - name, arguments.
 
The name is the first word of the sentence. The arguments vary in number
and content, depending on the kind of command. Note that numeric delete
commands are represented this way, so (for example), after
    parse( [1], '.', Tree, ok )
Tree's name is 'delete'. I.e. the command "1" is represented as if it
were "delete 1".
 
This module exports selector predicates for accessing these components.
 
 
PUBLIC parse( Sentence+, Terminator+, Tree-, Error- ):
"Tree and Error are the result of parsing the input list Sentence whose
terminator is Terminator".
 
If parse detects errors, Tree is undefined and Error is a representation
of the error. Otherwise Tree is the parse-tree and Error is 'ok'.
 
 
PUBLIC parse_fact( Sentence+, Tree-, Error- ):
 
Sentence is assumed to be a fact, without its terminator, and without an
initial number. Tree and Error are unified with the result of parsing.
Tree will _not_ become a positioned fact, but an ordinary fact, as
defined by the editor.
 
 
PUBLIC parse_question( Sentence+, Tree-, Error- ):
 
Sentence is assumed to be a question, without its terminator. Tree and
Error are unified with the result of parsing.
 
 
PUBLIC is_positioned_fact( Term+ ):
PUBLIC is_command( Term+ ):
True if Term is a positioned fact/command. For questions and naked
facts, see EDITOR.
 
 
PUBLIC positioned_fact_vs_position_and_fact( PFact+, Position-, Fact- ):
Selects the position and naked fact from a positioned fact.
 
 
PUBLIC command_vs_commandname( Command+, Word- ):
Word will be unified with an atom, the word that started the command.
*/
 
 
/*
IMPLEMENTATION
--------------
 
This is a straightforward DCG grammar. It deals mainly with commands;
facts and questions are relegated to other modules. The form of tokens
is defined by module TOKENISE.PL: note that (for example) variables
are represented as the structure 'var(Name)' and not just as an atom.
 
 
command/2 indexes on the first word of a command, selecting an
appropriate predicate to deal with all commands having the same
argument structure by calling 'command_table'. Note that, because
'command_table' is a DCG goal, it gets the input- and output-
list arguments appended when called.
    command( command(CommandName,Args), Error ) -->
        [ CommandName ],
        command_table( CommandName, Recogniser, Args, Error ),
        { call( Recogniser ) }.
 
This technique is similar to that in ``The Art of Prolog''.
 
 
The grammar tries to be specific about errors, rather than just
reporting (for example), "bad command argument". The second argument of
each rule head is an error indicator. This will become 'ok' if the rule
detects no error, otherwise some atom or structure denoting the error.
Errors are passed up from level to level so that they're directly
available from parse.
 
 
I have placed a cut at the first point in a rule where I know that an
alternative can't possibly succeed. Most are RED because the rules are
not mutually exclusive without the cuts.
 
 
Although it would be cleaner, I have avoided using the ( A-> B ; C )
construction in DCG rules, as it's not universally implemented, and some
systems provide no way of augmenting their DCG translator. Instead, I
have used ( A, B ; C ) with cuts where necessary.
*/
 
 
:- needs
    is_mode / 1,
    logic_fact_in / 4,
    logic_question_in / 4,
    output_to_chars / 2,
    prolog_fact_in / 4,
    prolog_question_in / 4,
    rem / 2,
    rem / 3.
 
 
parse( Sentence, Terminator, Tree, Error ) :-
    phrase( sentence(Tree,Error,Terminator), Sentence ).
 
 
sentence( C, E, _ ) -->
    command( C, E ), !.
sentence( pfact(fact([]),end), empty_fact, '.' ) -->
    [], !.
sentence( question([]), empty_question, '?' ) -->
    [], !.
sentence( C, E, '?' ) -->
    question( C, E ), !.
sentence( C, E, '.' ) -->
    nfact( C, E ).
/*
Note: this depends on the structure of the fact and question selectors
defined later.
*/
 
 
/*  Commands.  */
 
 
command( command(CommandName,Args), Error ) -->
    [ atom(CommandName) ],
    command_table( CommandName, Recogniser, Args, Error ),
    { call( Recogniser ) }.
 
 
command_table( again,   command_0(again,A,E,In,Out), A, E, In, Out ) :- !.
command_table( analyse, command_p_fqr(analyse,A,E,In,Out), A, E, In, Out ) :- !.
command_table( append,  command_f(append,A,E,In,Out), A, E, In, Out ) :- !.
command_table( ask,     command_0(ask,A,E,In,Out), A, E, In, Out ) :- !.
command_table( bye,     command_0(bye,A,E,In,Out), A, E, In, Out ) :- !.
command_table( change,  command_str_str_fqr(change,A,E,In,Out), A, E, In, Out ) :- !.
command_table( copy,    command_fn_fn(copy,A,E,In,Out), A, E, In, Out ) :- !.
command_table( delete,  command_d(delete,A,E,In,Out), A, E, In, Out ) :- !.
command_table( edit,    command_fqr(edit,A,E,In,Out), A, E, In, Out ) :- !.
command_table( explain, command_0(explain,A,E,In,Out), A, E, In, Out ) :- !.
command_table( load,    command_f(load,A,E,In,Out), A, E, In, Out ) :- !.
command_table( logic,   command_0(logic,A,E,In,Out), A, E, In, Out ) :- !.
command_table( next,    command_0(next,A,E,In,Out), A, E, In, Out ) :- !.
command_table( prolog,  command_0(prolog,A,E,In,Out), A, E, In, Out ) :- !.
command_table( restore, command_f(restore,A,E,In,Out), A, E, In, Out ) :- !.
command_table( save,    command_f(save,A,E,In,Out), A, E, In, Out ) :- !.
command_table( script,  command_scr(script,A,E,In,Out), A, E, In, Out ) :- !.
command_table( section, command_sec(section,A,E,In,Out), A, E, In, Out ) :- !.
command_table( show,    command_p_fqr(show,A,E,In,Out), A, E, In, Out ) :- !.
command_table( trace,   command_optional_p(trace,A,E,In,Out), A, E, In, Out ) :- !.
command_table( why,     command_optional_p(why,A,E,In,Out), A, E, In, Out ) :- !.
 
 
command_0( C, [], ok ) -->
    [], !.
 
command_0( C, error, C+excess_after ) -->
    rem.
 
 
command_optional_p( C, Printer, ok ) -->
    printer_name( Printer ), !.
 
command_optional_p( C, screen, ok ) -->
    [], !.
 
command_optional_p( C, error, C+bad_printer_name/R ) -->
    rem(R).
 
 
command_f( C, File, ok ) -->
    file_name( File ), !.
 
command_f( C, error, C+bad_file_name/R ) -->
    rem(R).
 
 
command_scr( C, Script, ok ) -->
    script_name( Script ), !.
 
command_scr( C, error, C+bad_script_name/R ) -->
    rem(R).
 
 
command_sec( C, Section, E ) -->
    section_number( Section, E0 ), {addc( C,E0, E )}, !.
 
command_sec( C, error, C+bad_section_number/R ) -->
    rem(R).
 
 
command_fn_fn( C, from_to(From,To), ok ) -->
    fact_number(From), [atom(to)], fact_number(To), !.
 
command_fn_fn( C, error, C+bad_fact_number_2/N ) -->
    fact_number(_), [atom(to)], rem(N), !.
 
command_fn_fn( C, from_to(From,end), ok ) -->
    fact_number(From), !.
 
command_fn_fn( C, error, C+bad_fact_number_1/N ) -->
    rem(N).
 
 
command_fqr( C, R, E ) -->
    fact_or_question_range( R, E0 ), !, {addc( C,E0, E )}.
 
command_fqr( C, R, C+bad_fact_or_question_range/R ) -->
    rem(R).
 
 
command_d( C, empty, ok ) -->
    [], !.
 
command_d( C, R, E ) -->
    fact_range(R,E0), !, {addc( C,E0, E )}.
 
command_d( C, F, E ) -->
    fact(F,E0), !, {addc( C,E0, E )}.
 
 
command_str_str_fqr( C, change(S1,S2,R), E ) -->
    change_string(S1), [atom(to)],
    change_string(S2), [atom(in)],
    fact_or_question_range(R,E0), !, {addc( C,E0, E )}.
 
command_str_str_fqr( C, error, C+bad_fact_or_question_range/R ) -->
    change_string(S1), [atom(to)],
    change_string(S2), [atom(in)],
    rem(R), !.
 
command_str_str_fqr( C, error, C+missing_in ) -->
    change_string(S1), [atom(to)],
    change_string(S2), rem, !.
 
command_str_str_fqr( C, error, C+dud_replacement/A ) -->
    change_string(S1), [atom(to)],
    rem(A), !.
 
command_str_str_fqr( C, error, C+missing_to ) -->
    change_string(S1), rem, !.
 
command_str_str_fqr( C, error, C+dud_replacee/A ) -->
    rem(A), !.
 
 
command_p_fqr( C, show(screen, all), ok ) -->
    [], !.
 
command_p_fqr( C, show(Printer, Range), E ) -->
    printer_name( Printer ),
    (
        fact_or_question_range(Range,E0),
        { addc( C, E0, E ) }
    ;
        { Range=all, E=ok }
    ), !.
 
command_p_fqr( C, show(screen, Range), E ) -->
    fact_or_question_range(Range,E0), {addc( C,E0, E )}.
 
command_p_fqr( C, error, C+bad_range_or_printer_name/[W] ) -->
    [W], rem.
 
 
/*  Questions and facts.  */
 
question( Q, E ) -->
    { is_mode(logic) }, !, logic_question_in(Q,E).
 
question( Q, E ) -->
    prolog_question_in(Q,E).
 
 
nfact( command(delete,range(N,N)), ok ) -->
    fact_number(N), !.
 
nfact( pfact(F,N), E ) -->
    fact_number(N), !, fact( F, E ).
 
nfact( pfact(F,end), E ) -->
    fact( F, E ).
 
 
fact( F, E ) -->
    { is_mode(logic) }, !, logic_fact_in( F, E ).
 
fact( F, E ) -->
    prolog_fact_in( F, E ).
 
 
/*  The next two are exported.  */
 
parse_fact( Tokens, F, E ) :-
    fact( F, E, Tokens, [] ).
 
parse_question( Tokens, Q, E ) :-
    question( Q, E, Tokens, [] ).
 
 
/* Auxiliary rules.  */
 
fact_or_question_range( q, ok ) -->
    [atom(q)], !.
 
fact_or_question_range( R, Status ) -->
    fact_range( R, Status ).
 
 
fact_range( all, ok ) -->
    [atom(all)], !.
 
fact_range( pred(Name), ok ) -->
    [atom(Name)], !.
 
fact_range( R, E ) -->
    fact_number(L), [atom(/)], !, fact_number(U),
    { check_range( L, U, R, E ) }.
 
fact_range( range(I,I), ok ) -->
    fact_number(I).
 
 
check_range( L, U, error, l_not_integer/L ) :-
    not( integer(L) ), !.
 
check_range( L, U, error, u_not_integer/L ) :-
    not( integer(U) ), !.
 
check_range( L, U, error, wrong_order/[L,U] ) :-
    L > U, !.
 
check_range( L, U, range(L,U), ok ).
 
 
fact_number(N) -->
    [ integer(N) ], !.
fact_number(Nm) -->
    [ atom(-), integer(N) ], { Nm is -N }.
    /*  This latter is so the user can handle negative facts added
        by 'adda'.
    */
 
 
section_number(N,ok) -->
    [ integer(N) ], { N>0 }, !.
section_number(N,section_zero) -->
    [ integer(0) ].
 
 
script_name(S) -->
    file_name(S).
 
 
change_string(A) -->
    [ atom(A) ], !.
 
change_string(A) -->
    [ var(A) ], !.
 
change_string(A) -->
    [ quoted_atom(A) ], !.
 
change_string(A) -->
    [ string(S) ], { name(A,S) }.
 
change_string(A) -->
    ( [ integer(N) ] ; [ real(N) ] ),
    !,
    { output_to_chars(N,S), name(A,S) }.
 
 
file_name( A ) -->
    [atom(A)].
 
 
printer_name(ctc)   --> [atom(ctc)].
printer_name(ep)    --> [atom(ep)].
printer_name(oucs)  --> [atom(oucs)].
printer_name(ss)    --> [atom(ss)].
 
 
/*  Errors.  */
 
 
/*  addc( CommandName+, Error0+, Error1- ):
        If Error0 is not 'ok', stick the CommandName onto it giving
        Error1, otherwise Error1 = Error0.
*/
addc( _, ok, ok ) :- !.
addc( C, E, C+E ).
 
 
/*  Selectors.  */
 
is_positioned_fact( pfact(_,_) ).
 
 
is_command( command(_,_) ).
 
 
positioned_fact_vs_position_and_fact( pfact(F,P), P, F ).
 
 
command_vs_commandname( command(W,_), W ).
 
 
:- endmodule.
