View source with formatted comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        jan@swi-prolog.org
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  1985-2021, University of Amsterdam,
    7                              VU University Amsterdam
    8                              SWI-Prolog Solutions b.v.
    9    All rights reserved.
   10
   11    Redistribution and use in source and binary forms, with or without
   12    modification, are permitted provided that the following conditions
   13    are met:
   14
   15    1. Redistributions of source code must retain the above copyright
   16       notice, this list of conditions and the following disclaimer.
   17
   18    2. Redistributions in binary form must reproduce the above copyright
   19       notice, this list of conditions and the following disclaimer in
   20       the documentation and/or other materials provided with the
   21       distribution.
   22
   23    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   24    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   25    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   26    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   27    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   28    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   29    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   31    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   33    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   34    POSSIBILITY OF SUCH DAMAGE.
   35*/
   36
   37:- module(prolog_explain,
   38          [ explain/1,
   39            explain/2
   40          ]).   41:- autoload(library(apply),[maplist/2,maplist/3]).   42:- autoload(library(lists),[flatten/2]).   43:- autoload(library(prolog_code), [pi_head/2]).   44
   45:- if(exists_source(library(pldoc/man_index))).   46:- autoload(library(pldoc/man_index), [man_object_property/2]).   47:- endif.   48
   49/** <module> Describe Prolog Terms
   50
   51The   library(explain)   describes   prolog-terms.   The   most   useful
   52functionality is its cross-referencing function.
   53
   54```
   55?- explain(subset(_,_)).
   56"subset(_, _)" is a compound term
   57    from 2-th clause of lists:subset/2
   58    Referenced from 46-th clause of prolog_xref:imported/3
   59    Referenced from 68-th clause of prolog_xref:imported/3
   60lists:subset/2 is a predicate defined in
   61    /staff/jan/lib/pl-5.6.17/library/lists.pl:307
   62    Referenced from 2-th clause of lists:subset/2
   63    Possibly referenced from 2-th clause of lists:subset/2
   64```
   65
   66Note that PceEmacs can jump to definitions   and gxref/0 can be used for
   67an overview of dependencies.
   68*/
   69
   70%!  explain(@Term) is det
   71%
   72%   Give an explanation on Term. The  argument   may  be any Prolog data
   73%   object. If the argument is an atom,  a term of the form `Name/Arity`
   74%   or a term of the form   `Module:Name/Arity`, explain/1 describes the
   75%   predicate as well as possible references to it. See also gxref/0.
   76
   77explain(Item) :-
   78    explain(Item, Explanation),
   79    print_message(information, explain(Explanation)),
   80    fail.
   81explain(_).
   82
   83                /********************************
   84                *           BASIC TYPES         *
   85                *********************************/
   86
   87%!  explain(@Term, -Explanation) is nondet.
   88%
   89%   True when Explanation is an explanation of Term. The explaination is
   90%   a list of elements that  is printed using print_message(information,
   91%   explain(Explanation)).
   92
   93explain(Var, [isa(Var, 'unbound variable')]) :-
   94    var(Var),
   95    !.
   96explain(I, [isa(I, 'an integer')]) :-
   97    integer(I),
   98    !.
   99explain(F, [isa(F, 'a floating point number')]) :-
  100    float(F),
  101    !.
  102explain(Q, [isa(Q, 'a rational (Q) number')]) :-
  103    rational(Q),
  104    !.
  105explain(S, [isa(S, 'a string')]) :-
  106    string(S),
  107    !.
  108explain([], [isa([], 'a special constant denoting an empty list')]) :-
  109    !.
  110explain(A, [isa(A, 'an atom')]) :-
  111    atom(A).
  112explain(A, Explanation) :-
  113    atom(A),
  114    current_op(Pri, F, A),
  115    op_type(F, Type),
  116    Explanation = [ isa(A, 'a ~w (~w) operator of priority ~d'-[Type, F, Pri]) ].
  117explain(A, Explanation) :-
  118    atom(A),
  119    !,
  120    explain_atom(A, Explanation).
  121explain([H|T], Explanation) :-
  122    List = [H|T],
  123    is_list(T),
  124    !,
  125    length(List, L),
  126    (   Explanation = [ isa(List, 'a proper list with ~d elements'-[L]) ]
  127    ;   maplist(printable, List),
  128        Explanation = [ indent, 'Text is "~s"'-[List] ]
  129    ).
  130explain(List, Explanation) :-
  131    List = [_|_],
  132    !,
  133    length(List, L),
  134    !,
  135    Explanation = [isa(List, 'is a not-closed list with ~d elements'-[L])].
  136explain(Name//NTArity, Explanation) :-
  137    atom(Name),
  138    integer(NTArity),
  139    NTArity >= 0,
  140    !,
  141    Arity is NTArity + 2,
  142    explain(Name/Arity, Explanation).
  143explain(Name/Arity, Explanation) :-
  144    atom(Name),
  145    integer(Arity),
  146    Arity >= 0,
  147    !,
  148    functor(Head, Name, Arity),
  149    known_predicate(Module:Head),
  150    (   Module == system
  151    ->  true
  152    ;   \+ predicate_property(Module:Head, imported_from(_))
  153    ),
  154    explain_predicate(Module:Head, Explanation).
  155explain(Module:Name/Arity, Explanation) :-
  156    atom(Module), atom(Name), integer(Arity),
  157    !,
  158    functor(Head, Name, Arity),
  159    explain_predicate(Module:Head, Explanation).
  160explain(Module:Head, Explanation) :-
  161    callable(Head),
  162    !,
  163    explain_predicate(Module:Head, Explanation).
  164explain(Dict, Explanation) :-
  165    is_dict(Dict, Tag),
  166    !,
  167    Explanation = [isa(Dict, 'a dict with tag ~q'-[Tag]) ].
  168explain(Term, Explanation) :-
  169    compound(Term),
  170    compound_name_arity(Term, _Name, Arity),
  171    numbervars(Term, 0, _, [singletons(true)]),
  172    Explanation = [isa(Term, 'is a compound term with arity ~D'-[Arity])].
  173explain(Term, Explanation) :-
  174    explain_functor(Term, Explanation).
  175
  176%!  known_predicate(:Head)
  177%
  178%   Succeeds if we know anything about this predicate.  Undefined
  179%   predicates are considered `known' for this purpose, so we can
  180%   provide referenced messages on them.
  181
  182known_predicate(M:Head) :-
  183    var(M),
  184    current_predicate(_, M2:Head),
  185    (   predicate_property(M2:Head, imported_from(M))
  186    ->  true
  187    ;   M = M2
  188    ),
  189    !.
  190known_predicate(Pred) :-
  191    predicate_property(Pred, undefined).
  192known_predicate(_:Head) :-
  193    functor(Head, Name, Arity),
  194    '$in_library'(Name, Arity, _Path).
  195
  196op_type(X, prefix) :-
  197    atom_chars(X, [f, _]).
  198op_type(X, infix) :-
  199    atom_chars(X, [_, f, _]).
  200op_type(X, postfix) :-
  201    atom_chars(X, [_, f]).
  202
  203printable(C) :-
  204    integer(C),
  205    code_type(C, graph).
  206
  207
  208                /********************************
  209                *             ATOMS             *
  210                *********************************/
  211
  212explain_atom(A, Explanation) :-
  213    referenced(A, Explanation).
  214explain_atom(A, Explanation) :-
  215    current_predicate(A, Module:Head),
  216    (   Module == system
  217    ->  true
  218    ;   \+ predicate_property(Module:Head, imported_from(_))
  219    ),
  220    explain_predicate(Module:Head, Explanation).
  221explain_atom(A, Explanation) :-
  222    predicate_property(Module:Head, undefined),
  223    functor(Head, A, _),
  224    explain_predicate(Module:Head, Explanation).
  225
  226
  227                /********************************
  228                *            FUNCTOR             *
  229                *********************************/
  230
  231explain_functor(Head, Explanation) :-
  232    referenced(Head, Explanation).
  233explain_functor(Head, Explanation) :-
  234    current_predicate(_, Module:Head),
  235    \+ predicate_property(Module:Head, imported_from(_)),
  236    explain_predicate(Module:Head, Explanation).
  237explain_functor(Head, Explanation) :-
  238    predicate_property(M:Head, undefined),
  239    (   functor(Head, N, A),
  240        Explanation = [ pi(M:N/A), 'is an undefined predicate' ]
  241    ;   referenced(M:Head, Explanation)
  242    ).
  243
  244
  245                /********************************
  246                *           PREDICATE           *
  247                *********************************/
  248
  249lproperty(built_in,     [' built-in']).
  250lproperty(dynamic,      [' dynamic']).
  251lproperty(multifile,    [' multifile']).
  252lproperty(transparent,  [' meta']).
  253
  254tproperty(Pred, [' imported from module ', module(Module)]) :-
  255    predicate_property(Pred, imported(Module)).
  256tproperty(Pred, [' defined in ', url(File:Line)]) :-
  257    predicate_property(Pred, file(File)),
  258    predicate_property(Pred, line_count(Line)).
  259tproperty(Pred, [' that can be autoloaded']) :-
  260    predicate_property(Pred, autoload).
  261
  262%!  explain_predicate(:Head, -Explanation) is det.
  263
  264explain_predicate(Pred, Explanation) :-
  265    Pred = Module:Head,
  266    functor(Head, Name, Arity),
  267    (   predicate_property(Pred, non_terminal)
  268    ->  What = 'non-terminal'
  269    ;   What = 'predicate'
  270    ),
  271    (   predicate_property(Pred, undefined)
  272    ->  Explanation = [ pi(Module:Name/Arity),
  273                        ansi([bold,fg(default)], ' is an undefined ~w', [What])
  274                      ]
  275    ;   (   var(Module)
  276        ->  U0 = [ pi(Name/Arity),
  277                   ansi([bold,fg(default)], ' is a', [])
  278                 ]
  279        ;   U0 = [ pi(Module:Name/Arity),
  280                   ansi([bold,fg(default)], ' is a', [])
  281                 ]
  282        ),
  283        findall(Utter, (lproperty(Prop, Utter),
  284                        predicate_property(Pred, Prop)),
  285                U1),
  286        U2 = [ansi([bold,fg(default)], ' ~w', [What]) ],
  287        findall(Utter, tproperty(Pred, Utter),
  288                U3),
  289        flatten([U0, U1, U2, U3], Explanation)
  290    ).
  291:- if(current_predicate(man_object_property/2)).  292explain_predicate(Pred, Explanation) :-
  293    Pred = _Module:Head,
  294    functor(Head, Name, Arity),
  295    man_object_property(Name/Arity, summary(Summary)),
  296    source_file(Pred, File),
  297    current_prolog_flag(home, Home),
  298    sub_atom(File, 0, _, _, Home),
  299    Explanation = [indent, 'Summary: "~w"'-[Summary] ].
  300:- endif.  301explain_predicate(Pred, Explanation) :-
  302    referenced(Pred, Explanation).
  303
  304                /********************************
  305                *          REFERENCES           *
  306                *********************************/
  307
  308referenced(Term, Explanation) :-
  309    current_predicate(_, Module:Head),
  310    (   predicate_property(Module:Head, built_in)
  311    ->  current_prolog_flag(access_level, system)
  312    ;   true
  313    ),
  314    \+ predicate_property(Module:Head, imported_from(_)),
  315    Module:Head \= help_index:predicate(_,_,_,_,_),
  316    nth_clause(Module:Head, N, Ref),
  317    '$xr_member'(Ref, Term),
  318    utter_referenced(Module:Head, N, Ref,
  319                     'Referenced', Explanation).
  320referenced(_:Head, Explanation) :-
  321    current_predicate(_, Module:Head),
  322    (   predicate_property(Module:Head, built_in)
  323    ->  current_prolog_flag(access_level, system)
  324    ;   true
  325    ),
  326    \+ predicate_property(Module:Head, imported_from(_)),
  327    nth_clause(Module:Head, N, Ref),
  328    '$xr_member'(Ref, Head),
  329    utter_referenced(Module:Head, N, Ref,
  330                     'Possibly referenced', Explanation).
  331
  332utter_referenced(_Module:class(_,_,_,_,_,_), _, _, _, _) :-
  333    current_prolog_flag(xpce, true),
  334    !,
  335    fail.
  336utter_referenced(_Module:lazy_send_method(_,_,_), _, _, _, _) :-
  337    current_prolog_flag(xpce, true),
  338    !,
  339    fail.
  340utter_referenced(_Module:lazy_get_method(_,_,_), _, _, _, _) :-
  341    current_prolog_flag(xpce, true),
  342    !,
  343    fail.
  344utter_referenced(From, _, _, _, _) :-
  345    hide_reference(From),
  346    !,
  347    fail.
  348utter_referenced(pce_xref:defined(_,_,_), _, _, _, _) :-
  349    !,
  350    fail.
  351utter_referenced(pce_xref:called(_,_,_), _, _, _, _) :-
  352    !,
  353    fail.
  354utter_referenced(pce_principal:send_implementation(_, _, _),
  355                 _, Ref, Text, Explanation) :-
  356    current_prolog_flag(xpce, true),
  357    !,
  358    xpce_method_id(Ref, Id),
  359    Explanation = [indent, '~w from ~w'-[Text, Id]].
  360utter_referenced(pce_principal:get_implementation(Id, _, _, _),
  361                 _, Ref, Text, Explanation) :-
  362    current_prolog_flag(xpce, true),
  363    !,
  364    xpce_method_id(Ref, Id),
  365    Explanation = [indent, '~w from ~w'-[Text, Id]].
  366utter_referenced(Head, N, Ref, Text, Explanation) :-
  367    clause_property(Ref, file(File)),
  368    clause_property(Ref, line_count(Line)),
  369    !,
  370    pi_head(PI, Head),
  371    Explanation = [ indent,
  372                    '~w from ~d-th clause of '-[Text, N],
  373                    pi(PI), ' at ', url(File:Line)
  374                  ].
  375utter_referenced(Head, N, _Ref, Text, Explanation) :-
  376    pi_head(PI, Head),
  377    Explanation = [ indent,
  378                    '~w from ~d-th clause of '-[Text, N],
  379                    pi(PI)
  380                  ].
  381
  382xpce_method_id(Ref, Id) :-
  383    clause(Head, _Body, Ref),
  384    strip_module(Head, _, H),
  385    arg(1, H, Id).
  386
  387hide_reference(pce_xref:exported(_,_)).
  388hide_reference(pce_xref:defined(_,_,_)).
  389hide_reference(pce_xref:called(_,_,_)).
  390hide_reference(prolog_xref:called(_,_,_,_,_)).
  391hide_reference(prolog_xref:pred_mode(_,_,_)).
  392hide_reference(prolog_xref:exported(_,_)).
  393hide_reference(prolog_xref:dynamic(_,_,_)).
  394hide_reference(prolog_xref:imported(_,_,_)).
  395hide_reference(prolog_xref:pred_comment(_,_,_,_)).
  396hide_reference(_:'$mode'(_,_)).
  397hide_reference(_:'$pldoc'(_,_,_,_)).
  398hide_reference(prolog_manual_index:man_index(_,_,_,_,_)).
  399
  400
  401                /********************************
  402                *           MESSAGES            *
  403                *********************************/
  404
  405:- multifile
  406    prolog:message//1.  407
  408prolog:message(explain(Explanation)) -->
  409    report(Explanation).
  410
  411report(Explanation) -->
  412    { string(Explanation),
  413      !,
  414      split_string(Explanation, "\n", "", Lines)
  415    },
  416    lines(Lines).
  417report(Explanation) -->
  418    { is_list(Explanation) },
  419    report_list(Explanation).
  420
  421lines([]) -->
  422    [].
  423lines([H]) -->
  424    !,
  425    [ '~s'-[H] ].
  426lines([H|T]) -->
  427    [ '~s'-[H], nl ],
  428    lines(T).
  429
  430report_list([]) -->
  431    [].
  432report_list([H|T]) -->
  433    report1(H),
  434    report_list(T).
  435
  436report1(indent) -->
  437    !,
  438    [ '~t~6|'-[] ].
  439report1(String) -->
  440    { atomic(String) },
  441    [ '~w'-[String] ].
  442report1(Fmt-Args) -->
  443    !,
  444    [ Fmt-Args ].
  445report1(url(Location)) -->
  446    [ url(Location) ].
  447report1(url(URL, Label)) -->
  448    [ url(URL, Label) ].
  449report1(pi(PI)) -->
  450    { pi_nt(PI, NT) },
  451    [ ansi(code, '~q', [NT]) ].
  452report1(ansi(Style, Fmt, Args)) -->
  453    [ ansi(Style, Fmt, Args) ].
  454report1(isa(Obj, Fmt-Args)) -->
  455    !,
  456    [ ansi(code, '~p', [Obj]),
  457      ansi([bold,fg(default)], ' is ', []),
  458      ansi([bold,fg(default)], Fmt, Args)
  459    ].
  460report1(isa(Obj, Descr)) -->
  461    [ ansi(code, '~p', [Obj]),
  462      ansi([bold,fg(default)], ' is ~w', [Descr])
  463    ].
  464
  465pi_nt(Module:Name/Arity, NT),
  466    atom(Module), atom(Name), integer(Arity),
  467    Arity >= 2,
  468    functor(Head, Name, Arity),
  469    predicate_property(Module:Head, non_terminal) =>
  470    Arity2 is Arity - 2,
  471    NT = Module:Name//Arity2.
  472pi_nt(PI, NT) =>
  473    NT = PI