/*  USEFUL.PL  */

/*
:- module useful.


:- public real/1,
          numeric/1,
          max/3,
          min/3,
          abolish/2,
          clause_vs_head_tail_pred_arity/5,
          clause_vs_head_tail/3,
          clause_vs_pred_arity/3,
          conj_vs_list/2,
          round_term_vs_list/4,
          is_system_pred/2,
          is_system_goal/1,
          get_prompt/1,
          set_prompt/1,
          add_portray/1,
          del_portray/1.
 */

/*
SPECIFICATION
-------------

This module exports a few generally useful predicates.

The first two predicates, 'real' and 'numeric' provide type-tests that
ought to exist in Poplog Prolog (or any other implementation with real
numbers) but are unaccountably absent.

'abolish' ought also to exist in your Prolog, as it does in mine. If it
isn't there, use the definition below.

'max' and 'min' do what you'd expect, as do 'set/get_prompt' and
'is_system_pred/goal'.

'head_tail_arity_pred' is useful for taking clauses apart and
decomposing their heads, as are its relatives.

'conj_vs_list' converts between "round lists" (conjunctions of goals)
and lists.

'round_term_vs_list' does the same, but the operator need not be comma.
So it can convert between, e.g. (a+b+c+d) and [a,b,c,d].

'add/del_portray' make it easy to add and delete clauses for portray.

The module also defines ^ as the exponentiation operator. In Poplog, for
some reason, although ^ has been defined as an operator (xfy with
precedence 10), it is not recognised by "is", which expects **.


PUBLIC real( X+ ):
"X is a real number". You may have this built-in, possibly under the
name of "float".


PUBLIC numeric( X+ ):
"X is an integer or real". You may have this built-in, possibly under
the name of "number".


PUBLIC max( A+, B+, C? ):
"C is the maximum of A and B".


PUBLIC min( A+, B+, C? ):
"C is the minimum of A and B".


PUBLIC clause_vs_head_tail_pred_arity( Clause?, H?, T?, F?, A? ):
"Clause has head H with functor F and arity A, and tail T".

If Clause is instantiated, and is not of the form _:-_, then T is
unified with 'true', and H with Clause.
Going in the reverse direction, if T is 'true', Clause is unified with H
and not with H:-true.


PUBLIC clause_vs_head_tail( Clause?, H?, T? ):
"Clause has head H and tail T".


PUBLIC clause_vs_pred_arity( Clause?, P?, A? ):
"Clause's head has predicate P and arity A".


PUBLIC conj_vs_list( Goals?, List? ):
"The list of goals corresponding to Goals is List".

E.g.
    conj_vs_list( (a,b,c), L )  -  L = [a,b,c]
    conj_vs_list( G, [a,b,c] )  -  G = (a,b,c)

If a goal is 'true', it will be omitted from the list:
    conj_vs_list( (true,a,b,true,c,true), L )  -  L = [a,b,c]


PUBLIC round_term_vs_list( Term?, List?, Op+, Ident+ ):

This does the same as conj_vs_list, but can accept operators other
than comma.
E.g.
    round_term_vs_list( (a+b+c), L, +, '' ).
    - gives L = [a,b,c]
    round_term_vs_list( G, [a,b,c], +, '' ).
    - gives G = a+(b+c)

If one of the subterms of Term is Ident, it will be omitted from the
list. This is a generalisation of what conj_vs_list does with 'true'.
Thus:
    round_term_vs_list( (0+a+b+0+c+0), L, +, 0 ).
    - gives L = [a,b,c]


PUBLIC abolish( P+, A+ ):
"There are no clauses for P/A".

Removes all clauses with head predicate P, arity A.


PUBLIC is_system_pred( P+, A+ ):
True if P/A is a system predicate.


PUBLIC is_system_goal( G+ ):
True if G calls a system predicate.


PUBLIC get_prompt( P? ):
Unifies the current prompt used when reading characters or terms
interactively to P (as an atom).


PUBLIC set_prompt( P+ ):
Sets the current prompt used when reading characters or terms
interactively to P (as an atom).


PUBLIC add_portray( P+ ):
"The clause
    portray(X) :- P(X)
has been asserted".

If there is no such clause, assert one.


PUBLIC del_portray( P+ ):
"The clause
    portray(X) :- P(X)
has been deleted".

If there is such a clause, delete it.
*/


/*
IMPLEMENTATION
--------------

'real' is missing from Poplog Prolog. It can be implemented easily by
calling the built-in routine dataword which tells us what type its
argument is. If it's real, dataword will return either 'decimal' or
'ddecimal' (the latter for double-precision reals. If your system lacks
such a predicate, but has real numbers, then you can probably do
    real(X) :- atomic(X), not( integer(X) ), not( atom(X) ).

The prompt predicates reset or examine the Poplog system variable
prolog_read_prompt which contains the current prompt. If you can't
change your read prompt (perhaps by calling a foreign routine and
overwriting it), you'll just have to alter the scripts to fit what your
system imposes.

'is_system_pred' calls the Poplog predicate 'prolog_system_predicate'.
If you don't have such a predicate, you could make a look-up table of
all your system predicates (with arities) and check against that.

The definition of 'abolish' is commented out because my system has it.

'add/del_portray' call add/del_linking_clause from LIB.PL.

Making ^ do exponentiation relies on the fact that in Poplog, the right
hand side of "is" can evaluate any functor that's a Pop-11 function
name. So all we need do is, in USEFUL.P, to make ^ a function with the
same effect as **. ^ is already defined as an operator of the correct
fix and precedence. Note that exponentiation is referred to in the
lesson on arithmetic; if you don't have ^ as exponentiation, and can't
make it, you'll need to change the script. The same goes for "mod"
incidentally.
*/

/*
:- needs    add_linking_clause / 3,
            bug / 2,
            del_linking_clause / 3.
*/

/*  USEFUL.P is needed for the prompt.  */
:- pop_compile( 'useful.p' ).


real(R) :-
    prolog_eval( dataword(quote(R)), D ),
    ( D = decimal ; D = ddecimal ), !.


numeric(N) :- integer(N), !.
numeric(N) :- real(N).


max( A, B, C ) :- A >= B, !, C=A.
max( A, B, B ).

min( A, B, C ) :- A >= B, !, C=B.
min( A, B, A ).


/*
abolish( P, A ) :-
    functor( Head, P, A ),
    retractall( Head ).
*/


clause_vs_head_tail_pred_arity( H, H, true, F, A ) :-
    H \= ( _ :- _ ),
    !,
    functor( H, F, A ).

clause_vs_head_tail_pred_arity( (H:-T), H, T, F, A ) :-
    functor( H, F, A ).


clause_vs_head_tail( Clause, H, T ) :-
    clause_vs_head_tail_pred_arity( Clause, H, T, _, _ ).


clause_vs_pred_arity( Clause, P, A ) :-
    clause_vs_head_tail_pred_arity( Clause, _, _, P, A ).


conj_vs_list( C, L ) :-
    round_term_vs_list( C, L, ',', true ).


round_term_vs_list( C, L, Op, Ident ) :-
    round_term_vs_list( C, [], L, Op, Ident ).


round_term_vs_list( G, L0, [G|L0], Op, Ident ) :-
    functor( Form, Op, 2 ),
    G \= Form,
    G \= Ident,
    !.

round_term_vs_list( Ident, L, L, _, Ident ) :- !.

round_term_vs_list( Term, L0, L, Op, Ident ) :-
    functor( Form, Op, 2 ),
    arg( 1, Form, A ),
    arg( 2, Form, B ),
    Term = Form,
    round_term_vs_list( A, L1, L, Op, Ident ),
    round_term_vs_list( B, L0, L1, Op, Ident ).


is_system_pred( P, A ) :-
    prolog_system_predicate( P, A ).


is_system_goal( G ) :-
    functor( G, P, A ),
    is_system_pred( P, A ).


get_prompt( P ) :-
    prolog_eval( apply(valof(get_prompt)), P ).


set_prompt( P ) :-
    not( atom(P) ),
    bug( 'set_prompt: not an atom', P ).

set_prompt( P ) :-
    prolog_eval( set_prompt(P) ).


add_portray(P) :-
    add_linking_clause( portray, P, 1 ).


del_portray(P) :-
    del_linking_clause( portray, P, 1 ).

/*
:- endmodule.
*/
