/*****************************************************************************
 * This file is part of the Prolog Development Tool (PDT)
 *
 * WWW: http://sewiki.iai.uni-bonn.de/research/pdt/start
 * Mail: pdt@lists.iai.uni-bonn.de
 * Copyright (C): 2004-2012, CS Dept. III, University of Bonn
 *
 * Authors: Eva Stoewe, Guenter Kniesel and Jan Wielemaker
 *
 * All rights reserved. This program is  made available under the terms
 * of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 ****************************************************************************/

:- module(prolog_metainference,
	  [ infer_meta_predicate/2,		% :Head, -MetaSpec
	    inferred_meta_predicate/2		% :Head, ?MetaSpec
	  ]).
:- use_module(library(lists)).
:- use_module(library(apply)).

:- meta_predicate
	inferred_meta_predicate(:, ?),
	infer_meta_predicate(:, -).

:- dynamic
	inferred_meta_pred/3.			% Head, Module, Meta

/** <module> Infer meta-predicate properties

This module infers meta-predicate properties   by inspecting the clauses
of predicates that call other predicates.   This is extremely useful for
program analysis and refactoring because  many   programs  `in the wild'
have incomplete or incorrect meta-predicate information.

@see	This library is used by prolog_walk_code/1 to improve the
	accuracy of this analysis.
@tbd	Re-introduce some alias-analysis
@tbd	Not all missing meta-declarations are interesting.  Notably,
	meta-predicates that are private and only pass meta-arguments
	on behalve of a public meta-predicates do not need a declaration.
*/


%%	inferred_meta_predicate(:Head, ?MetaSpec) is nondet.
%
%	True when MetaSpec is an   inferred meta-predicate specification
%	for Head.

inferred_meta_predicate(M:Head, MetaSpec) :-
	inferred_meta_pred(Head, M, MetaSpec).
inferred_meta_predicate(M:Head, MetaSpec) :-
	predicate_property(M:Head, imported_from(From)),
	inferred_meta_pred(Head, From, MetaSpec).


%%	infer_meta_predicate(:Head, -MetaSpec) is semidet
%
%	True  when  MetaSpec  is  a  meta-predicate  specifier  for  the
%	predicate Head. Derived meta-predicates are   collected and made
%	available through inferred_meta_predicate/2.

infer_meta_predicate(Head, MetaSpec) :-
	inferred_meta_predicate(Head, MetaSpec), !.
infer_meta_predicate(M:Head, MetaSpec) :-
	predicate_property(M:Head, imported_from(From)), !,
	do_infer_meta_predicate(From:Head, MetaSpec),
	assertz(inferred_meta_pred(Head, From, MetaSpec)).
infer_meta_predicate(M:Head, MetaSpec) :-
	do_infer_meta_predicate(M:Head, MetaSpec),
	assertz(inferred_meta_pred(Head, M, MetaSpec)).

:- meta_predicate
	do_infer_meta_predicate(:, -).

do_infer_meta_predicate(Module:AHead, MetaSpec):-
	functor(AHead, Functor, Arity),
	functor(Head, Functor, Arity),	% Generalise the head
	findall(MetaSpec,
		meta_pred_args_in_clause(Module, Head, MetaSpec),
		MetaSpecs),
	MetaSpecs \== [],
	combine_meta_args(MetaSpecs, MetaSpec).


%%	meta_pred_args_in_clause(+Module, +Head, -MetaSpec) is nondet.

meta_pred_args_in_clause(Module, Head, MetaArgs) :-
	clause(Module:Head, Body),
	annotate_meta_vars_in_body(Body, Module),
	meta_annotation(Head, MetaArgs).


%%	annotate_meta_vars_in_body(+Term, +Module) is det
%
%	Annotate variables in Term if they appear as meta-arguments.
%
%	@tbd	Aliasing.  Previous code detected aliasing for
%		- =/2
%		- functor/3
%		- atom_concat/3
%		- =../2
%		- arg/3
%	@tbd	We can make this nondet, exploring multiple aliasing
%		paths in disjunctions.

annotate_meta_vars_in_body(A, _) :-
	atomic(A), !.
annotate_meta_vars_in_body(Var, _) :-
	var(Var), !,
	annotate(Var, 0).
annotate_meta_vars_in_body(Module:Term, _) :- !,
	(   atom(Module)
	->  annotate_meta_vars_in_body(Term, Module)
	;   var(Module)
	->  annotate(Module, m)
	;   true			% may continue if Term is a system
	).				% predicate?
annotate_meta_vars_in_body((TermA, TermB), Module) :- !,
	annotate_meta_vars_in_body(TermB, Module),
	annotate_meta_vars_in_body(TermA, Module).
annotate_meta_vars_in_body((TermA; TermB), Module) :- !,
	annotate_meta_vars_in_body(TermB, Module),
	annotate_meta_vars_in_body(TermA, Module).
annotate_meta_vars_in_body((TermA->TermB), Module) :- !,
	annotate_meta_vars_in_body(TermB, Module),
	annotate_meta_vars_in_body(TermA, Module).
annotate_meta_vars_in_body((TermA*->TermB), Module) :- !,
	annotate_meta_vars_in_body(TermB, Module),
	annotate_meta_vars_in_body(TermA, Module).
annotate_meta_vars_in_body(A=B, _) :-
	var(A), var(B), !,
	A = B.
annotate_meta_vars_in_body(Goal, Module) :- % TBD: do we trust this?
	predicate_property(Module:Goal, meta_predicate(Head)), !,
	functor(Goal, _, Arity),
	annotate_meta_args(1, Arity, Goal, Head, Module).
annotate_meta_vars_in_body(Goal, Module) :-
	inferred_meta_predicate(Module:Goal, Head), !,
	functor(Goal, _, Arity),
	annotate_meta_args(1, Arity, Goal, Head, Module).
annotate_meta_vars_in_body(_, _).


%%	annotate_meta_args(+Arg, +Arity, +Goal, +MetaSpec, +Module)

annotate_meta_args(I, Arity, Goal, MetaSpec, Module) :-
	I =< Arity, !,
	arg(I, MetaSpec, MetaArg),
	arg(I, Goal, Arg),
	annotate_meta_arg(MetaArg, Arg, Module),
	I2 is I + 1,
	annotate_meta_args(I2, Arity, Goal, MetaSpec, Module).
annotate_meta_args(_, _, _, _, _).

annotate_meta_arg(Spec, Arg, _) :-
	var(Arg), !,
	annotate(Arg, Spec).
annotate_meta_arg(0, Arg, Module) :- !,
	annotate_meta_vars_in_body(Arg, Module).
annotate_meta_arg(N, Arg, Module) :-
	integer(N),
	callable(Arg), !,
	Arg =.. List,
	length(Extra, N),
	append(List, Extra, ListX),
	ArgX =.. ListX,
	annotate_meta_vars_in_body(ArgX, Module).
annotate_meta_arg(Spec, Arg, _) :-
	is_meta(Spec),
	compound(Arg),
	Arg = Module:_,
	var(Module), !,
	annotate(Module, m).
annotate_meta_arg(_,_,_).

annotate(Var, Annotation) :-
	get_attr(Var, prolog_metainference, Annot0), !,
	join_annotation(Annot0, Annotation, Joined),
	put_attr(Var, prolog_metainference, Joined).
annotate(Var, Annotation) :-
	put_attr(Var, prolog_metainference, Annotation).

join_annotation(A, A, A) :- !.
join_annotation(A, B, C) :-
	(   is_meta(A), \+ is_meta(B)
	->  C = A
	;   \+ is_meta(A), is_meta(B)
	->  C = B
	;   is_meta(A), is_meta(B)
	->  C = (:)
	;   C = *
	).

attr_unify_hook(A0, Other) :-
	get_attr(Other, prolog_metainference, A1), !,
	join_annotation(A0, A1, A),
	put_attr(Other, prolog_metainference, A).


%%	meta_annotation(+Head, -Annotation) is semidet.
%
%	True when Annotation is an   appropriate  meta-specification for
%	Head.

meta_annotation(Head, Meta) :-
	functor(Head, Name, Arity),
	functor(Meta, Name, Arity),
	meta_args(1, Arity, Head, Meta, HasMeta),
	HasMeta == true.

meta_args(I, Arity, Head, Meta, HasMeta) :-
	I =< Arity, !,
	arg(I, Head, HeadArg),
	arg(I, Meta, MetaArg),
	meta_arg(HeadArg, MetaArg),
	(   is_meta(MetaArg)
	->  HasMeta = true
	;   true
	),
	I2 is I + 1,
	meta_args(I2, Arity, Head, Meta, HasMeta).
meta_args(_, _, _, _, _).

is_meta(I) :- integer(I), !.
is_meta(:).
is_meta(^).
is_meta(//).

%%	meta_arg(+AnnotatedArg, -MetaSpec) is det.
%
%	True when MetaSpec is  a  proper   annotation  for  the argument
%	AnnotatedArg. This is simple if the argument is a plain argument
%	in the head (first clause). If it   is  a compound term, it must
%	unify to _:_, otherwise there is no point turning it into a meta
%	argument. If the  module  part  is   then  passed  to  a  module
%	sensitive predicate, we assume it is a meta-predicate.

meta_arg(HeadArg, MetaArg) :-
	get_attr(HeadArg, prolog_metainference, MetaArg),
	MetaArg \== m, !.
meta_arg(HeadArg, :) :-
	compound(HeadArg),
	HeadArg = M:_,
	get_attr(M, prolog_metainference, m), !.
meta_arg(_, *).

%%	combine_meta_args(+Heads, -Head) is det.
%
%	Combine multiple meta-specifications.

combine_meta_args([], []) :- !.
combine_meta_args([List], List) :- !.
combine_meta_args([Spec,Spec|Specs], CombinedArgs) :- !,
	combine_meta_args([Spec|Specs], CombinedArgs).
combine_meta_args([Spec1,Spec2|Specs], CombinedArgs) :-
	Spec1 =.. [Name|Args1],
	Spec2 =.. [Name|Args2],
	maplist(join_annotation, Args1, Args2, Args),
	Spec =.. [Name|Args],
	combine_meta_args([Spec|Specs], CombinedArgs).


