/*  $Id: explain.pl,v 1.6 1998/02/18 13:56:36 jan Exp $

    Part of SWI-Prolog
    Designed and implemented by Jan Wielemaker
    E-mail: jan@swi.psy.uva.nl

    Copyright (C) 1996 University of Amsterdam. All rights reserved.
*/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The   library(explain)   describes   prolog-terms.   The   most   useful
functionality is its cross-referencing function.

Note  that  the  help-tool  for   XPCE    provides   a   nice  graphical
cross-referencer.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

:- module(explain,
	  [ explain/1,
	    explain/2
	  ]).
:- use_module(library(helpidx)).

explain(Item) :-
	explain(Item, Explanation),
	write_ln(Explanation),
	fail.
explain(_).

		/********************************
		*           BASIC TYPES         *
		*********************************/

explain(Var, Explanation) :-
	var(Var), !,
	utter(Explanation, '"~w" is an unbound variable', [Var]).
explain(I, Explanation) :-
	integer(I), !,
	utter(Explanation, '"~w" is an integer', [I]).
explain(F, Explanation) :-
	float(F), !,
	utter(Explanation, '"~w" is a floating point number', [F]).
explain(S, Explanation) :-
	string(S), !,
	utter(Explanation, '"~w" is a string', S).
explain([], Explanation) :- !,
	utter(Explanation, '"[]" is an atom denoting an empty list', []).
explain(A, Explanation) :-
	atom(A),
	utter(Explanation, '"~w" is an atom', [A]).
explain(A, Explanation) :-
	current_op(Pri, F, A),
	op_type(F, Type),
	utter(Explanation, '"~w" is a ~w (~w) operator of priority ~d',
	      [A, Type, F, Pri]).
explain(A, Explanation) :-
	atom(A), !,
	explain_atom(A, Explanation).
explain([H|T], Explanation) :-
	proper_list(T), !,
	List = [H|T],
	length(List, L),
	(   utter(Explanation, '"~p" is a proper list with ~d elements',
	          [List, L])
	;   checklist(printable, List),
	    utter(Explanation, '~t~8|Text is "~s"',  [List])
	).
explain([H|T], Explanation) :- !,
	length([H|T], L), !,
	utter(Explanation, '"~p" is a not-closed list with ~d elements',
	      [[H|T], L]).
explain(Name/Arity, Explanation) :-
	atom(Name),
	integer(Arity), !,
	functor(Head, Name, Arity),
	current_predicate(_, Module:Head),
	(   Module == system
	->  true
	;   \+ predicate_property(Module:Head, imported_from(_))
	),
	explain_predicate(Module:Head, Explanation).
explain(Term, Explanation) :-
	utter(Explanation, '"~w" is a compound term', [Term]).
explain(Term, Explanation) :-
	explain_functor(Term, Explanation).
	
op_type(X, prefix) :-
	atom_chars(X, [0'f, _]).
op_type(X, infix) :-
	atom_chars(X, [_, 0'f, _]).
op_type(X, postfix) :-
	atom_chars(X, [_, 0'f]).

printable(C) :-
	integer(C),
	between(32, 126, C).

		/********************************
		*             ATOMS             *
		*********************************/

explain_atom(A, Explanation) :-
	referenced(A, Explanation).
explain_atom(A, Explanation) :-
	current_predicate(A, Module:Head),
	(   Module == system
	->  true
	;   \+ predicate_property(Module:Head, imported_from(_))
	),
	explain_predicate(Module:Head, Explanation).

		/********************************
		*            FUNCTOR             *
		*********************************/

explain_functor(Head, Explanation) :-
	referenced(Head, Explanation).
explain_functor(Head, Explanation) :-
	current_predicate(_, Module:Head),
	\+ predicate_property(Module:Head, imported_from(_)),
	explain_predicate(Module:Head, Explanation).
explain_functor(Head, Explanation) :-
	predicate_property(M:Head, undefined),
	(   functor(Head, N, A),
	    utter(Explanation, '~w:~w/~d is an undefined predicate', [M,N,A])
	;   referenced(M:Head, Explanation)
	).
	
	
		/********************************
		*           PREDICATE           *
		*********************************/

lproperty(built_in,	' built-in', []).
lproperty(dynamic,	' dynamic', []).
lproperty(multifile,	' multifile', []).
lproperty(transparent,	' meta', []).

tproperty(imported_from(Module), ' imported from module ~w', [Module]).
tproperty(file(File),		' defined in~n~t~8|~w', [File]).
tproperty(line_count(Number),	':~d', [Number]).

combine_utterances(Pairs, Explanation) :-
	maplist(first, Pairs, Fmts),
	concat_atom(Fmts, Format),
	maplist(second, Pairs, ArgList),
	flatten(ArgList, Args),
	utter(Explanation, Format, Args).

first(A-_B, A).
second(_A-B, B).

explain_predicate(Pred, Explanation) :-
	Pred = Module:Head,
	functor(Head, Name, Arity),
	
	U0 = '~w:~w/~d is a' - [Module, Name, Arity],
	findall(Fmt-Arg, (lproperty(Prop, Fmt, Arg),
			  predicate_property(Pred, Prop)),
		U1),
 	U2 = ' predicate' - [],
	findall(Fmt-Arg, (tproperty(Prop, Fmt, Arg),
			  predicate_property(Pred, Prop)),
		U3),
	flatten([U0, U1, U2, U3], Utters),
	combine_utterances(Utters, Explanation).
explain_predicate(Pred, Explanation) :-
	predicate_property(Pred, built_in),
	Pred = _Module:Head,
	functor(Head, Name, Arity),
	predicate(Name, Arity, Summary, _, _),
	utter(Explanation, '~t~8|Summary: ``~w''''', [Summary]).
explain_predicate(Pred, Explanation) :-
	referenced(Pred, Explanation).
	
		/********************************
		*          REFERENCES           *
		*********************************/

referenced(Term, Explanation) :-
	current_predicate(_, Module:Head),
	\+ predicate_property(Module:Head, built_in),
	\+ predicate_property(Module:Head, imported_from(_)),
	Module:Head \= help_index:predicate(_,_,_,_,_),
	Head \= '$user_query'(_,_),
	nth_clause(Module:Head, N, Ref),
	'$xr_member'(Ref, Term),
	utter_referenced(Module:Head, N, Ref,
			 'Referenced', Explanation).

referenced(_Module:Head, Explanation) :-
	current_predicate(_, Module:Head),
	\+ predicate_property(Module:Head, built_in),
	\+ predicate_property(Module:Head, imported_from(_)),
	nth_clause(Module:Head, N, Ref),
	'$xr_member'(Ref, Head),
	utter_referenced(Module:Head, N, Ref,
			 'Possibly referenced', Explanation).

utter_referenced(_Module:class(_,_,_,_,_,_), _, _, _, _) :-
	feature(xpce, true), !,
	fail.
utter_referenced(_Module:lazy_send_method(_,_,_), _, _, _, _) :-
	feature(xpce, true), !,
	fail.
utter_referenced(_Module:lazy_get_method(_,_,_), _, _, _, _) :-
	feature(xpce, true), !,
	fail.
utter_referenced(Module:Head, _N, Ref, Text, Explanation) :-
	feature(xpce, true),
	functor(Head, Name, _Arity),
	concat(send_, Class, Name),
	selector(Ref, Selector),
	check_xpce_method(Module, Class, send, Selector), !,
	utter(Explanation,
	      '~t~8|~w from ~w->~w',
	      [Text, Class, Selector]).
utter_referenced(Module:Head, _N, Ref, Text, Explanation) :-
	feature(xpce, true),
	functor(Head, Name, _Arity),
	concat(get_, Class, Name),
	selector(Ref, Selector),
	check_xpce_method(Module, Class, get, Selector), !,
	utter(Explanation,
	      '~t~8|~w from ~w<-~w',
	      [Text, Class, Selector]).
utter_referenced(Module:Head, N, _Ref, Text, Explanation) :-
	functor(Head, Name, Arity),
	utter(Explanation,
	      '~t~8|~w from ~d-th clause of ~w:~w/~d',
	      [Text, N, Module, Name, Arity]).
	
selector(Ref, Selector) :-
	clause(Head, _Body, Ref),
	'$strip_module'(Head, _, Plain),
	arg(1, Plain, Selector),
	atom(Selector).

%	Verifies the detection of a clause implementing an XCE method.

check_xpce_method(Module, Class, send, Selector) :-
	catch(Module:lazy_send_method(Selector, Class, _), _, fail).
check_xpce_method(Module, Class, get, Selector) :-
	catch(Module:lazy_get_method(Selector, Class, _), _, fail).

		/********************************
		*             UTTER            *
		*********************************/

utter(Explanation, Fmt, Args) :-
	sformat(Explanation, Fmt, Args).
