/***************************************************************************** * 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 /** 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).