/*  PARSE_PROLOG.PL  */
 
 
:- module parse_prolog.
 
 
:- public prolog_fact_in/4,
          prolog_question_in/4.
 
 
/*
SPECIFICATION
-------------
 
This module exports prolog_fact_in/4, a DCG predicate for parsing facts
expressed in Prolog, such as
    loves( john, mary ) :- loves( mary, john )
and prolog_question_in/4, a similar DCG predicate for parsing questions.
 
These may appear to be trivial (unlike the predicates exported from
PARSE_LOGIC), because of the existence of read/1. There are two things
that make them less so. Firstly, we need to diagnose errors - see
PARSE_LOGIC for a discussion of this point. Secondly, we want to turn
a list of tokens into a term, and there is no standard way to do this.
 
 
PUBLIC DCG prolog_fact_in( Tree-, Error- ):
PUBLIC DCG prolog_question_in( Tree-, Error- ):
-------------------------------------------
 
"Tree and Error are the result of parsing a list of tokens representing
a Prolog term, according to the standard syntax of Prolog".
 
If the tokens represent a syntactically valid term, Tree will be unified
with a 'parse tree'. This will contain the term itself, and a dictionary
of variable names, acessible by the selector predicates defined in
READTERM. In this case, Error will be 'ok'. Otherwise, Tree will be
undefined and Error will be a representation of the error.
*/
 
 
/*
IMPLEMENTATION
--------------
 
Parsing and error diagnosis
---------------------------
 
Essentially, what we're doing with prolog_fact_in is to start with a list
of tokens which we hope represents a valid Prolog term. We submit this
list (call it Tokens) to
    tokens_to_term( Tokens, Term, Vars )
which is a predicate similar in intention to read/1, but capable of
reading terms from a token list rather than a file.
 
tokens_to_term also has to return a list of variable-name associations,
See PARSE_LOGIC for more on this.
 
If tokens_to_term succeeds, a valid term was read. 'check_clause' is
then called to look for possible errors in it. Again, see PARSE_LOGIC
for comments.
 
prolog_question_in works in exactly the same way as prolog_fact_in.
*/
 
 
:- needs
    check_clause / 2,
    check_goal / 2,
    define_syntax_error / 3,
    fact_vs_clause_and_vars / 3,
    fact_vs_text / 2,
    question_vs_goal_and_vars / 3,
    question_vs_text / 2,
    rem / 3,
    standardise_clause / 2,
    tokens_to_term / 3.
 
 
:- dynamic
    '$users_syntax_error' / 2.
 
 
prolog_fact_in( Fact, Error ) -->
    rem( Tokens ),
    {
        (
            tokens_to_term( Tokens, Clause, Vars )
        ->
            (
                check_clause( Clause, Error )
            ->
                fact_vs_text( Fact, Tokens )
            ;
                fact_vs_clause_and_vars( Fact, Clause_, Vars ),
                standardise_clause( Clause, Clause_ ),
                Error = ok
            )
        ;
            syntax_error_details( Message, Input ),
            fact_vs_text( Fact, Tokens ),
            Error = bad_prolog_fact( Message, Input )
        )
    }.
 
 
prolog_question_in( Question, Error ) -->
    rem( Tokens ),
    {
        (
            tokens_to_term( Tokens, Goals, Vars )
        ->
            (
                check_goal( Goals, Error )
            ->
                question_vs_text( Question, Tokens )
            ;
                question_vs_goal_and_vars( Question, Goals, Vars ),
                Error = ok
            )
        ;
            syntax_error_details( Message, Input ),
            question_vs_text( Question, Tokens ),
            Error = bad_prolog_question( Message, Input )
        )
    }.
 
 
/*
Syntax errors.
--------------
 
We catch these by redefining READ_TERM's syntax-error predicate to
save the message M and input I.                          
*/
 
 
catch_syntax_error( M, I, _ ) :-
    asserta( '$users_syntax_error'(M,I) ).
 
 
syntax_error_details( M, I ) :-
    '$users_syntax_error'(M,I),
    retractall( '$users_syntax_error'(_,_) ).
 
 
:- define_syntax_error( catch_syntax_error ).
 
 
:- endmodule.
