/***********************************************************************

                              DCG Compiler

***********************************************************************/


/*======================================================================
                         Operator Declarations
======================================================================*/

:- op(1200,xfx,'--->').

:- dynamic (--->)/2, parse/3, connect/3.


/*======================================================================
                            Compiler Driver
======================================================================*/

%%% compile
%%% =======
%%%
%%%        Generates compiled clauses by partial execution of the DCG 
%%%        metainterpreter below, and adds them to the Prolog database.

compile :-
    program_clause(Clause),
    partially_execute(Clause, CompiledClause),
    add_rule(CompiledClause),
    fail.


%%% add_rule(Clause)
%%% ================
%%%
%%%        Clause  ==>  clause to be added to database after rewriting into
%%%                     a normal form that changes calls to parse into
%%%                     calls on particular nonterminals

add_rule((Head :- Body)) :-
    rewrite(Head, NewHead),
    rewrite(Body, NewBody),
    write('Asserting "'), write((NewHead :- NewBody)), write('."'), nl,
    assert((NewHead :- NewBody)).


%%% rewrite(Term, NewTerm)
%%% ======================
%%%
%%%        Term     ==> a term encoding a literal or sequence of literals
%%%        NewTerm  <== the term rewritten so literals of the form
%%%                        parse(s(...),...)
%%%                     are rewritten into the form
%%%                        s(...,...)

rewrite((A,B), (C,D)) :- !,
    rewrite(A, C), rewrite(B, D).
rewrite(parse(Term, P1, P2), NewLiteral) :- !,
    Term =.. [Function|Args],
    conc(Args, [P1, P2], AllArgs),
    NewLiteral =.. [Function|AllArgs].
rewrite(Term,Term).


/*======================================================================
                 Partial Execution of Prolog Programs
======================================================================*/

%%% partially_execute(Term, NewTerm) 
%%% ================================ 
%%% 
%%%        Term    ==> term encoding Prolog clause, literal list or literal
%%%                    to be partially executed with respect to the program 
%%%                    clauses and auxiliary clauses given by program_clause
%%%                    and clause predicates respectively.
%%%        NewTerm <== the partially executed term.

%%% Partially executing a clause involves expanding the body.
partially_execute((Head:-Body), (Head:-ExpandedBody)) :- !,
    partially_execute(Body, ExpandedBody).

%%% Partially expanding a literal list involves conjoining the expansions
%%% of the respective expansions.
partially_execute((Literal, Rest), Expansion) :- !,
    % expand the first literal
    partially_execute(Literal, ExpandedLiteral),
    % and the rest of them
    partially_execute(Rest, ExpandedRest),
    % and conjoin the results
    conjoin(ExpandedLiteral, ExpandedRest, Expansion).

%%% Partially executing an auxiliary literal involves replacing it with
%%% the body of a matching clause (if there are any).
partially_execute(Literal, Expansion) :-
    % if the literal should be partially executed
    aux_literal(Literal),
    % and at least one rule matches
    setof(Some, Literal^aclause(Literal, Some), [_Clause|_Others]), !,
    % then pick up any rule 
    aclause(Literal, Body),
    % and expand its body
    partially_execute(Body, Expansion).

%%% Otherwise (if the literal is not an auxiliary literal or if no rules 
%%% match) we just leave it alone.
partially_execute(Literal, Literal).


/*----------------------------------------------------------------------
                               Utilities
----------------------------------------------------------------------*/

%%% conjoin(Conjunct1, Conjunct2, Conjunction)
%%% ==========================================
%%%
%%%        Conjunct1   ==>  two terms to be conjoined
%%%        Conjunct2   ==>
%%%        Conjunction <==  result of the conjunction

%%% Conjoining a conjunction works just like concatenation (conc).
conjoin((A,B), C, ABC) :- !,
    conjoin(B, C, BC),
    conjoin(A, BC, ABC).

%%% Conjoining true and anything leaves the other conjunct unchanged.
conjoin(true, A, A) :- !.
conjoin(A, true, A) :- !.

%%% Otherwise, use the normal comma conjunction operator.
conjoin(A, C, (A,C)).


%%% conc(List1, List2, List)
%%% ========================
%%%
%%%        List1 ==> a list
%%%        List2 ==> a list
%%%        List  <== the concatenation of the two lists

conc([], List, List).

conc([Element|Rest], List, [Element|LongRest]) :-
    conc(Rest, List, LongRest).


%%% aclause(Head, Body)
%%% ===================
%%%
%%%        Head <== the head and body of a clause encoded with the unary 
%%%        Body <== predicate `clause';  unit clauses can be encoded directly 
%%%                 with clause and the Body returned will be `true'.

aclause(Head, Body) :-
     clause((Head:-Body)) ; (clause(Head), Body = true).

/*======================================================================
                      Program to Partially Execute
======================================================================*/


/*----------------------------------------------------------------------
                Control Information for Partial Executor
----------------------------------------------------------------------*/

aux_literal( (_ ---> _)     ).
aux_literal( parse(_, _, _) ).


/*----------------------------------------------------------------------
	      DCG Metainterpreter to be Partially Executed
		      Encoded form of Program 6.3
----------------------------------------------------------------------*/


program_clause((        parse(NT, P_0, P) :-
                           (NT ---> Body), 
                           parse(Body, P_0, P)                    )).

program_clause((        connect(W, [W|R], R) :- true              )).


clause((                parse((Body1, Body2), P_0, P) :-
                            parse(Body1, P_0, P_1), 
                            parse(Body2, P_1, P)                  )).

clause((                parse([], P, P)                           )).

clause((                parse([Word|Rest], P_0, P) :-
                            connect(Word, P_0, P_1), 
                            parse(Rest, P_1, P)                   )).

clause((                parse({Goals}, P, P) :- call(Goals)       )).



/*----------------------------------------------------------------------
              Sample Data for Program to Partially Execute:
               The parse tree building DCG of Program 3.9
----------------------------------------------------------------------*/

clause((                s(s(NP,VP)) ---> np(NP), vp(VP)           )).
clause((                np(np(Det,N,Rel)) ---> det(Det), 
                                              n(N), 
                                              optrel(Rel)         )).
clause((                np(np(PN)) ---> pn(PN)                    )).
clause((                vp(vp(TV,NP)) ---> tv(TV), np(NP)         )).
clause((                vp(vp(IV)) ---> iv(IV)                    )).
clause((                optrel(rel(epsilon)) ---> []              )).
clause((                optrel(rel(that,VP)) ---> [that], vp(VP)  )).

clause((                pn(pn(terry)) ---> [terry]                )).
clause((                pn(pn(shrdlu)) ---> [shrdlu]              )).
clause((                iv(iv(halts)) ---> [halts]                )).
clause((                det(det(a)) ---> [a]                      )).
clause((                n(n(program)) ---> [program]              )).
clause((                tv(tv(writes)) ---> [writes]              )).
