/* $Id$ Part of SWI-Prolog Author: Jan Wielemaker E-mail: wielemak@science.uva.nl WWW: http://www.swi.psy.uva.nl/projects/xpce/ Copyright (C): 1985-2006, University of Amsterdam This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA As a special exception, if you link this library with other files, compiled with a Free Software compiler, to produce an executable, this library does not by itself cause the resulting executable to be covered by the GNU General Public License. This exception does not however invalidate any other reasons why the executable file might be covered by the GNU General Public License. */ :- module(prolog_xref, [ xref_source/1, % +Source xref_called/3, % ?Source, ?Callable, ?By xref_defined/3, % ?Source. ?Callable, -How xref_definition_line/2, % +How, -Line xref_exported/2, % ?Source, ?Callable xref_module/2, % ?Source, ?Module xref_op/2, % ?Source, ?Op xref_clean/1, % +Source xref_current_source/1, % ?Source xref_done/2, % +Source, -Time xref_built_in/1, % ?Callable xref_expand/2, % +Term, -Expanded xref_source_file/3, % +Spec, -Path, +Source xref_source_file/4, % +Spec, -Path, +Source, +Options xref_public_list/4, % +Path, -Export, +Src xref_meta/2, % +Goal, -Called xref_hook/1, % ?Callable % XPCE class references xref_used_class/2, % ?Source, ?ClassName xref_defined_class/3 % ?Source, ?ClassName, -How ]). :- use_module(library(debug), [debug/3, debugging/1]). :- use_module(library(lists), [append/3, member/2]). :- use_module(library(operators), [pop_operators/0, push_op/3, push_operators/1]). :- if(current_prolog_flag(dialect, swi)). :- use_module(library(shlib), [current_foreign_library/2]). :- endif. :- use_module(library(prolog_source)). :- use_module(library(option)). :- use_module(library(error)). :- dynamic called/3, % Head, Src, From (dynamic)/3, % Head, Src, Line (thread_local)/3, % Head, Src, Line (multifile)/3, % Head, Src, Line defined/3, % Head, Src, Line foreign/3, % Head, Src, Line constraint/3, % Head, Src, Line imported/3, % Head, Src, From exported/2, % Head, Src xmodule/2, % Module, Src xop/2, % Src, Op source/2, % Src, Time used_class/2, % Name, Src defined_class/5, % Name, Super, Summary, Src, Line (mode)/2. % Mode, Src :- create_prolog_flag(xref, false, [type(boolean)]). /******************************* * HOOKS * *******************************/ % prolog:called_by(+Goal, -ListOfCalled) % % If this succeeds, the cross-referencer assumes Goal may call any % of the goals in ListOfCalled. If this call fails, default % meta-goal analysis is used to determine additional called goals. % prolog:meta_goal(+Goal, -Pattern) % % Define meta-predicates. See the examples in this file for details. :- multifile prolog:called_by/2, % +Goal, -Called prolog:meta_goal/2, % +Goal, -Pattern prolog:hook/1. % +Callable :- dynamic meta_goal/2. called_by(Goal, Called) :- prolog:called_by(Goal, Called), !. called_by(on_signal(_,_,New), [New+1]) :- ( new == throw ; new == default ), !, fail. /******************************* * BUILT-INS * *******************************/ %% built_in_predicate(+Callable) % % True if Callable is a built-in :- expects_dialect(swi). :- if(current_prolog_flag(dialect, swi)). system_predicate(Goal) :- functor(Goal, Name, Arity), current_predicate(system:Name/Arity), % avoid autoloading predicate_property(system:Goal, built_in), !. :-endif. /******************************** * TOPLEVEL * ********************************/ verbose :- debugging(xref). %% xref_source(+Source) is det. % % Generate the cross-reference data for Source if not already % done and the source is not modified. Checking for modifications % is only done for files. % % @param Source File specification or XPCE buffer xref_source(Source) :- prolog_canonical_source(Source, Src), ( atom(Src) -> time_file(Src, Modified), source(Src, Modified) ), !. xref_source(Source) :- prolog_canonical_source(Source, Src), xref_clean(Src), ( atom(Src) -> time_file(Src, Modified) ; get_time(Modified) % Actually should be `generation' ), assert(source(Src, Modified)), xref_setup(Src, In, State), call_cleanup(collect(Src, In), xref_cleanup(State)). :- thread_local xref_stream/1. % input stream xref_setup(Src, In, state(In, Xref, [SRef|HRefs])) :- prolog_open_source(Src, In), asserta(xref_stream(In), SRef), ( current_prolog_flag(xref, Xref) -> true ; Xref = false ), set_prolog_flag(xref, true), ( verbose -> HRefs = [] ; asserta(user:message_hook(_,_,_), Ref), HRefs = [Ref] ). xref_cleanup(state(In, Xref, Refs)) :- prolog_close_source(In), set_prolog_flag(xref, Xref), maplist(erase, Refs). %% xref_input_stream(-Stream) is det. % % Current input stream for cross-referencer. xref_input_stream(Stream) :- xref_stream(Var), !, Stream = Var. %% xref_push_op(Source, +Prec, +Type, :Name) % % Define operators into the default source module and register % them to be undone by pop_operators/0. xref_push_op(Src, P, T, N0) :- !, ( N0 = _:_ -> N = N0 ; '$set_source_module'(M, M), N = M:N0 ), push_op(P, T, N), assert_op(Src, op(P,T,N)), debug(xref, ':- ~w.', [op(P,T,N)]). %% xref_clean(+Source) is det. % % Reset the database for the given source. xref_clean(Source) :- prolog_canonical_source(Source, Src), retractall(called(_, Src, _Origin)), retractall(dynamic(_, Src, Line)), retractall(multifile(_, Src, Line)), retractall(defined(_, Src, Line)), retractall(foreign(_, Src, Line)), retractall(constraint(_, Src, Line)), retractall(imported(_, Src, _From)), retractall(exported(_, Src)), retractall(xmodule(_, Src)), retractall(xop(Src, _)), retractall(source(Src, _)), retractall(used_class(_, Src)), retractall(defined_class(_, _, _, Src, _)), retractall(mode(_, Src)). /******************************* * READ RESULTS * *******************************/ %% xref_current_source(?Source) % % Check what sources have been analysed. xref_current_source(Source) :- source(Source, _Time). %% xref_done(+Source, -Time) is det. % % Cross-reference executed at Time xref_done(Source, Time) :- prolog_canonical_source(Source, Src), source(Src, Time). %% xref_called(+Source, ?Called, ?By) is nondet. % % Enumerate the predicate-call relations. Predicate called by % directives have a By ''. xref_called(Source, Called, By) :- prolog_canonical_source(Source, Src), called(Called, Src, By). %% xref_defined(+Source, +Goal, ?How) is semidet. % % Test if Goal is accessible in Source. If this is the case, How % specifies the reason why the predicate is accessible. Note that % this predicate does not deal with built-in or global predicates, % just locally defined and imported ones. xref_defined(Source, Called, How) :- prolog_canonical_source(Source, Src), xref_defined2(How, Src, Called). xref_defined2(dynamic(Line), Src, Called) :- dynamic(Called, Src, Line). xref_defined2(thread_local(Line), Src, Called) :- thread_local(Called, Src, Line). xref_defined2(multifile(Line), Src, Called) :- multifile(Called, Src, Line). xref_defined2(local(Line), Src, Called) :- defined(Called, Src, Line). xref_defined2(foreign(Line), Src, Called) :- foreign(Called, Src, Line). xref_defined2(constraint(Line), Src, Called) :- constraint(Called, Src, Line). xref_defined2(imported(From), Src, Called) :- imported(Called, Src, From). %% xref_definition_line(+How, -Line) % % If the 3th argument of xref_defined contains line info, return % this in Line. xref_definition_line(local(Line), Line). xref_definition_line(dynamic(Line), Line). xref_definition_line(thread_local(Line), Line). xref_definition_line(multifile(Line), Line). xref_definition_line(constraint(Line), Line). xref_definition_line(foreign(Line), Line). xref_exported(Source, Called) :- prolog_canonical_source(Source, Src), exported(Called, Src). %% xref_module(?Source, ?Module) is nondet. % % True if Module is defined in Source. xref_module(Source, Module) :- prolog_canonical_source(Source, Src), xmodule(Module, Src). %% xref_op(?Source, Op) is nondet. % % Give the operators active inside the module. This is intended to % setup the environment for incremental parsing of a term from the % source-file. % % @param Op Term of the form op(Priority, Type, Name) xref_op(Source, Op) :- prolog_canonical_source(Source, Src), xop(Src, Op). xref_built_in(Head) :- system_predicate(Head). xref_used_class(Source, Class) :- prolog_canonical_source(Source, Src), used_class(Class, Src). xref_defined_class(Source, Class, local(Line, Super, Summary)) :- prolog_canonical_source(Source, Src), defined_class(Class, Super, Summary, Src, Line), integer(Line), !. xref_defined_class(Source, Class, file(File)) :- prolog_canonical_source(Source, Src), defined_class(Class, _, _, Src, file(File)). collect(Src, In) :- repeat, catch(read_source_term(Src, In, Term, TermPos), E, report_syntax_error(E)), xref_expand(Term, T), ( T == end_of_file -> ! ; stream_position_data(line_count, TermPos, Line), flag(xref_src_line, _, Line), catch(process(T, Src), E, print_message(error, E)), fail ). %% read_source_term(+Src, +In:stream, -Term, -TermPos) is det. % % Read next term from In. The cross-referencer supports the % comment_hook as also implemented by the compiler for the % documentation processor. :- multifile prolog:comment_hook/3. read_source_term(Src, In, Term, TermPos) :- atom(Src), \+ source_file(Src), % normally loaded; no need to update '$get_predicate_attribute'(prolog:comment_hook(_,_,_), number_of_clauses, N), N > 0, !, '$set_source_module'(SM, SM), read_term(In, Term, [ term_position(TermPos), comments(Comments), module(SM) ]), ( catch(prolog:comment_hook(Comments, TermPos, Term), E, print_message(error, E)) -> true ; true ). read_source_term(_, In, Term, TermPos) :- '$set_source_module'(SM, SM), read_term(In, Term, [ term_position(TermPos), module(SM) ]). report_syntax_error(E) :- ( verbose -> print_message(error, E) ; true ), fail. /******************************* * EXPANSION * *******************************/ %% xref_expand(+Term, -Expanded) % % Do the term-expansion. We have to pass require as we need it for % validation. Otherwise we do term-expansion, handling all of the % XPCE class compiler as normal Prolog afterwards. CHR programs % are processed using process_chr/2 directly from the source, % which is why we inhibit expansion here. xref_expand((:- if(Cond)), (:- if(Cond))). xref_expand((:- elif(Cond)), (:- elif(Cond))). xref_expand((:- else), (:- else)). xref_expand((:- endif), (:- endif)). xref_expand((:- require(X)), (:- require(X))) :- !. xref_expand(Term, _) :- requires_library(Term, Lib), ensure_loaded(user:Lib), fail. xref_expand(Term, Term) :- chr_expandable(Term), !. xref_expand('$:-'(X), '$:-'(X)) :- !, % boot module style_check(+dollar). xref_expand(Term, T) :- catch(expand_term(Term, Expanded), _, Expanded=Term), ( is_list(Expanded) -> member(T, Expanded) ; T = Expanded ). %% requires_library(+Term, -Library) % % known expansion hooks. Should be more dynamic! requires_library((:- emacs_begin_mode(_,_,_,_,_)), library(emacs_extend)). requires_library((:- draw_begin_shape(_,_,_,_)), library(pcedraw)). /******************************* * PROCESS * *******************************/ process(Var, _) :- var(Var), !. % Warn? process((:- Directive), Src) :- !, process_directive(Directive, Src), !. process((?- Directive), Src) :- !, process_directive(Directive, Src), !. process((Head :- Body), Src) :- !, assert_defined(Src, Head), process_body(Body, Head, Src). process('$source_location'(_File, _Line):Clause, Src) :- !, process(Clause, Src). process(Term, Src) :- chr_expandable(Term), !, process_chr(Term, Src). process(M:(Head :- Body), Src) :- !, process((M:Head :- M:Body), Src). process(Head, Src) :- assert_defined(Src, Head). /******************************** * DIRECTIVES * ********************************/ process_directive(Var, _) :- var(Var), !. % error, but that isn't our business process_directive((A,B), Src) :- !, % TBD: whta about other control process_directive(A, Src), % structures? process_directive(B, Src). process_directive(List, Src) :- is_list(List), !, process_directive(consult(List), Src). process_directive(use_module(Spec, Import), Src) :- xref_public_list(Spec, Path, Public, Src), assert_import(Src, Import, Public, Path, false). process_directive(reexport(Spec, Import), Src) :- xref_public_list(Spec, Path, Public, Src), assert_import(Src, Import, Public, Path, true). process_directive(reexport(Modules), Src) :- process_use_module(Modules, Src, true). process_directive(use_module(Modules), Src) :- process_use_module(Modules, Src, false). process_directive(consult(Modules), Src) :- process_use_module(Modules, Src, false). process_directive(ensure_loaded(Modules), Src) :- process_use_module(Modules, Src, false). process_directive(load_files(Files, _Options), Src) :- process_use_module(Files, Src, false). process_directive(include(Files), Src) :- process_include(Files, Src). process_directive(dynamic(Dynamic), Src) :- assert_dynamic(Src, Dynamic). process_directive(thread_local(Dynamic), Src) :- assert_thread_local(Src, Dynamic). process_directive(multifile(Dynamic), Src) :- assert_multifile(Src, Dynamic). process_directive(module(Module, Export), Src) :- assert_module(Src, Module), assert_export(Src, Export). process_directive(system_mode(on), _Src) :- !, style_check(+dollar). process_directive(pce_begin_class_definition(Name, Meta, Super, Doc), Src) :- assert_defined_class(Src, Name, Meta, Super, Doc). process_directive(pce_autoload(Name, From), Src) :- assert_defined_class(Src, Name, imported_from(From)). process_directive(op(P, A, N), Src) :- xref_push_op(Src, P, A, N). process_directive(style_check(X), _) :- style_check(X). process_directive(encoding(Enc), _) :- ( xref_input_stream(Stream) -> catch(set_stream(Stream, encoding(Enc)), _, true) ; true % can this happen? ). process_directive(system_module, _) :- style_check(+dollar). process_directive(set_prolog_flag(character_escapes, Esc), _) :- set_prolog_flag(character_escapes, Esc). process_directive(pce_expansion:push_compile_operators, _) :- '$set_source_module'(SM, SM), call(pce_expansion:push_compile_operators(SM)). % call to avoid xref process_directive(pce_expansion:pop_compile_operators, _) :- call(pce_expansion:pop_compile_operators). process_directive(meta_predicate(Meta), _) :- process_meta_predicate(Meta). process_directive(arithmetic_function(FSpec), Src) :- arith_callable(FSpec, Goal), !, flag(xref_src_line, Line, Line), assert_called(Src, ''(Line), Goal). process_directive(format_predicate(_, Goal), Src) :- !, flag(xref_src_line, Line, Line), assert_called(Src, ''(Line), Goal). process_directive(if(Cond), Src) :- !, flag(xref_src_line, Line, Line), assert_called(Src, ''(Line), Cond). process_directive(elif(Cond), Src) :- !, flag(xref_src_line, Line, Line), assert_called(Src, ''(Line), Cond). process_directive(else, _) :- !. process_directive(endif, _) :- !. process_directive(Goal, Src) :- flag(xref_src_line, Line, Line), process_body(Goal, ''(Line), Src). %% process_meta_predicate(+Decl) % % Create prolog:meta_goal/2 declaration from the meta-goal % declaration. process_meta_predicate((A,B)) :- !, process_meta_predicate(A), process_meta_predicate(B). process_meta_predicate(Decl) :- functor(Decl, Name, Arity), functor(Head, Name, Arity), meta_args(1, Arity, Decl, Head, Meta), ( ( prolog:meta_goal(Head, _) ; called_by(Head, _) ; meta_goal(Head, _) ) -> true ; assert(meta_goal(Head, Meta)) ). meta_args(I, Arity, _, _, []) :- I > Arity, !. meta_args(I, Arity, Decl, Head, [H|T]) :- % 0 arg(I, Decl, 0), !, arg(I, Head, H), I2 is I + 1, meta_args(I2, Arity, Decl, Head, T). meta_args(I, Arity, Decl, Head, [H+A|T]) :- % I --> H+I arg(I, Decl, A), integer(A), A > 0, !, arg(I, Head, H), I2 is I + 1, meta_args(I2, Arity, Decl, Head, T). meta_args(I, Arity, Decl, Head, Meta) :- I2 is I + 1, meta_args(I2, Arity, Decl, Head, Meta). /******************************** * BODY * ********************************/ xref_meta((A, B), [A, B]). xref_meta((A; B), [A, B]). xref_meta((A| B), [A, B]). xref_meta((A -> B), [A, B]). xref_meta((A *-> B), [A, B]). xref_meta(findall(_V,G,_L), [G]). xref_meta(findall(_V,G,_L,_T), [G]). xref_meta(setof(_V, G, _L), [G]). xref_meta(bagof(_V, G, _L), [G]). xref_meta(forall(A, B), [A, B]). xref_meta(maplist(G,_), [G+1]). xref_meta(maplist(G,_,_), [G+2]). xref_meta(maplist(G,_,_,_), [G+3]). xref_meta(maplist(G,_,_,_,_), [G+4]). xref_meta(map_list_to_pairs(G,_,_), [G+2]). xref_meta(map_assoc(G, _), [G+1]). xref_meta(map_assoc(G, _, _), [G+2]). xref_meta(checklist(G, _L), [G+1]). xref_meta(sublist(G, _, _), [G+1]). xref_meta(include(G, _, _), [G+1]). xref_meta(exclude(G, _, _), [G+1]). xref_meta(partition(G, _, _, _, _), [G+2]). xref_meta(partition(G, _, _, _),[G+1]). xref_meta(call(G), [G]). xref_meta(call(G, _), [G+1]). xref_meta(call(G, _, _), [G+2]). xref_meta(call(G, _, _, _), [G+3]). xref_meta(call(G, _, _, _, _), [G+4]). xref_meta(not(G), [G]). xref_meta(notrace(G), [G]). xref_meta(\+(G), [G]). xref_meta(ignore(G), [G]). xref_meta(once(G), [G]). xref_meta(initialization(G), [G]). xref_meta(initialization(G,_), [G]). xref_meta(retract(Rule), [G]) :- head_of(Rule, G). xref_meta(clause(G, _), [G]). xref_meta(clause(G, _, _), [G]). xref_meta(phrase(G, _A), [G+2]). xref_meta(phrase(G, _A, _R), [G+2]). xref_meta(phrase_from_file(G,_),[G+2]). xref_meta(catch(A, _, B), [A, B]). xref_meta(thread_create(A,_,_), [A]). xref_meta(thread_signal(_,A), [A]). xref_meta(thread_at_exit(A), [A]). xref_meta(thread_initialization(A), [A]). xref_meta(predsort(A,_,_), [A+3]). xref_meta(call_cleanup(A, B), [A, B]). xref_meta(call_cleanup(A, _, B),[A, B]). xref_meta(setup_call_cleanup(A, B, C),[A, B, C]). xref_meta(setup_call_catcher_cleanup(A, B, _, C),[A, B, C]). xref_meta(with_mutex(_,A), [A]). xref_meta(assume(G), [G]). % library(debug) xref_meta(assertion(G), [G]). % library(debug) xref_meta(freeze(_, G), [G]). xref_meta(when(C, A), [C, A]). xref_meta(clause(G, _), [G]). xref_meta(clause(G, _, _), [G]). xref_meta(time(G), [G]). % development system xref_meta(profile(G), [G]). xref_meta(at_halt(G), [G]). xref_meta(call_with_time_limit(_, G), [G]). xref_meta(call_with_depth_limit(G, _, _), [G]). xref_meta(alarm(_, G, _), [G]). xref_meta(alarm(_, G, _, _), [G]). xref_meta('$add_directive_wic'(G), [G]). xref_meta(with_output_to(_, G), [G]). xref_meta(if(G), [G]). xref_meta(elif(G), [G]). xref_meta(meta_options(G,_,_), [G+1]). % XPCE meta-predicates xref_meta(pce_global(_, new(_)), _) :- !, fail. xref_meta(pce_global(_, B), [B+1]). xref_meta(ifmaintainer(G), [G]). % used in manual xref_meta(listen(_, G), [G]). % library(broadcast) xref_meta(listen(_, _, G), [G]). xref_meta(in_pce_thread(G), [G]). xref_meta(G, Meta) :- % call user extensions prolog:meta_goal(G, Meta). xref_meta(G, Meta) :- % Generated from :- meta_predicate meta_goal(G, Meta). %% head_of(+Rule, -Head) % % Get the head for a retract call. head_of(Var, _) :- var(Var), !, fail. head_of((Head :- _), Head). head_of(Head, Head). %% xref_hook(?Callable) % % Definition of known hooks. Hooks that can be called in any % module are unqualified. Other hooks are qualified with the % module where they are called. xref_hook(Hook) :- prolog:hook(Hook). xref_hook(Hook) :- hook(Hook). hook(attr_portray_hook(_,_)). hook(attr_unify_hook(_,_)). hook(goal_expansion(_,_)). hook(term_expansion(_,_)). hook(resource(_,_,_)). hook(emacs_prolog_colours:goal_classification(_,_)). hook(emacs_prolog_colours:term_colours(_,_)). hook(emacs_prolog_colours:goal_colours(_,_)). hook(emacs_prolog_colours:style(_,_)). hook(emacs_prolog_colours:identify(_,_)). hook(pce_principal:pce_class(_,_,_,_,_,_)). hook(pce_principal:send_implementation(_,_,_)). hook(pce_principal:get_implementation(_,_,_,_)). hook(pce_principal:pce_lazy_get_method(_,_,_)). hook(pce_principal:pce_lazy_send_method(_,_,_)). hook(pce_principal:pce_uses_template(_,_)). hook(prolog:locate_clauses(_,_)). hook(prolog:message(_,_,_)). hook(prolog:message_context(_,_,_)). hook(prolog:debug_control_hook(_)). hook(prolog:help_hook(_)). hook(prolog:show_profile_hook(_,_)). hook(prolog:general_exception(_,_)). hook(prolog_edit:load). hook(prolog_edit:locate(_,_,_)). hook(shlib:unload_all_foreign_libraries). hook(system:'$foreign_registered'(_, _)). hook(user:exception(_,_,_)). hook(user:file_search_path(_,_)). hook(user:library_directory(_)). hook(user:message_hook(_,_,_)). hook(user:portray(_)). hook(user:prolog_clause_name(_,_)). hook(user:prolog_list_goal(_)). hook(user:prolog_predicate_name(_,_)). hook(user:prolog_trace_interception(_,_,_,_)). hook(user:prolog_event_hook(_)). hook(user:prolog_exception_hook(_,_,_,_)). %% arith_callable(+Spec, -Callable) % % Translate argument of arithmetic_function/1 into a callable term arith_callable(Var, _) :- var(Var), !, fail. arith_callable(Module:Spec, Module:Goal) :- !, arith_callable(Spec, Goal). arith_callable(Name/Arity, Goal) :- PredArity is Arity + 1, functor(Goal, Name, PredArity). %% process_body(+Body, +Origin, +Src) % % Process a callable body (body of a clause or directive). Origin % describes the origin of the call. process_body(Var, _, _) :- var(Var), !. process_body(Goal, Origin, Src) :- called_by(Goal, Called), !, must_be(list, Called), assert_called(Src, Origin, Goal), process_called_list(Called, Origin, Src). process_body(Goal, Origin, Src) :- process_xpce_goal(Goal, Origin, Src), !. process_body(load_foreign_library(File), _Origin, Src) :- process_foreign(File, Src). process_body(load_foreign_library(File, _Init), _Origin, Src) :- process_foreign(File, Src). process_body(Goal, Origin, Src) :- xref_meta(Goal, Metas), !, assert_called(Src, Origin, Goal), process_called_list(Metas, Origin, Src). process_body(Goal, Origin, Src) :- asserting_goal(Goal, Rule), !, assert_called(Src, Origin, Goal), process_assert(Rule, Origin, Src). process_body(Goal, Origin, Src) :- assert_called(Src, Origin, Goal). process_called_list([], _, _). process_called_list([H|T], Origin, Src) :- process_meta(H, Origin, Src), process_called_list(T, Origin, Src). process_meta(A+N, Origin, Src) :- !, ( extend(A, N, AX) -> process_body(AX, Origin, Src) ; true ). process_meta(G, Origin, Src) :- process_body(G, Origin, Src). extend(Var, _, _) :- var(Var), !, fail. extend(M:G, N, M:GX) :- !, callable(G), extend(G, N, GX). extend(G, N, GX) :- callable(G), G =.. List, length(Rest, N), append(List, Rest, NList), GX =.. NList. asserting_goal(assert(Rule), Rule). asserting_goal(asserta(Rule), Rule). asserting_goal(assertz(Rule), Rule). asserting_goal(assert(Rule,_), Rule). asserting_goal(asserta(Rule,_), Rule). asserting_goal(assertz(Rule,_), Rule). process_assert(0, _, _) :- !. % catch variables process_assert((_:-Body), Origin, Src) :- !, process_body(Body, Origin, Src). process_assert(_, _, _). /******************************* * XPCE STUFF * *******************************/ pce_goal(new(_,_), new(-, new)). pce_goal(send(_,_), send(arg, msg)). pce_goal(send_class(_,_,_), send_class(arg, arg, msg)). pce_goal(get(_,_,_), get(arg, msg, -)). pce_goal(get_class(_,_,_,_), get_class(arg, arg, msg, -)). pce_goal(get_chain(_,_,_), get_chain(arg, msg, -)). pce_goal(get_object(_,_,_), get_object(arg, msg, -)). process_xpce_goal(G, Origin, Src) :- pce_goal(G, Process), !, assert_called(Src, Origin, G), ( arg(I, Process, How), arg(I, G, Term), process_xpce_arg(How, Term, Origin, Src), fail ; true ). process_xpce_arg(new, Term, Origin, Src) :- callable(Term), process_new(Term, Origin, Src). process_xpce_arg(arg, Term, Origin, Src) :- compound(Term), process_new(Term, Origin, Src). process_xpce_arg(msg, Term, Origin, Src) :- compound(Term), ( arg(_, Term, Arg), process_xpce_arg(arg, Arg, Origin, Src), fail ; true ). process_new(_M:_Term, _, _) :- !. % TBD: Calls on other modules! process_new(Term, Origin, Src) :- assert_new(Src, Origin, Term), ( arg(_, Term, Arg), process_xpce_arg(arg, Arg, Origin, Src), fail ; true ). assert_new(_, _, Term) :- \+ callable(Term), !. assert_new(Src, Origin, Control) :- functor(Control, Class, _), pce_control_class(Class), !, forall(arg(_, Control, Arg), assert_new(Src, Origin, Arg)). assert_new(Src, Origin, Term) :- compound(Term), arg(1, Term, Prolog), Prolog == @(prolog), ( Term =.. [message, _, Selector | T], atom(Selector) -> Called =.. [Selector|T], process_body(Called, Origin, Src) ; Term =.. [?, _, Selector | T], atom(Selector) -> append(T, [_R], T2), Called =.. [Selector|T2], process_body(Called, Origin, Src) ), fail. assert_new(_, _, @(_)) :- !. assert_new(Src, _, Term) :- functor(Term, Name, _), assert_used_class(Src, Name). pce_control_class(and). pce_control_class(or). pce_control_class(if). pce_control_class(not). /******************************** * INCLUDED MODULES * ********************************/ %% process_use_module(+Modules, +Src, +Rexport) is det. process_use_module(_Module:_Files, _, _) :- !. % loaded in another module process_use_module([], _, _) :- !. process_use_module([H|T], Src, Reexport) :- !, process_use_module(H, Src, Reexport), process_use_module(T, Src, Reexport). process_use_module(library(pce), Src, Reexport) :- !, % bit special xref_public_list(library(pce), Path, Public, Src), forall(member(Import, Public), process_pce_import(Import, Src, Path, Reexport)). process_use_module(File, Src, Reexport) :- ( catch(xref_public_list(File, Path, Public, Src), _, fail) -> assert_import(Src, Public, _, Path, Reexport), ( File = library(chr) % hacky -> assert(mode(chr, Src)) ; true ) ; true ). process_pce_import(Name/Arity, Src, Path, Reexport) :- atom(Name), integer(Arity), !, functor(Term, Name, Arity), ( \+ system_predicate(Term), \+ Term = pce_error(_) % hack!? -> assert_import(Src, [Name/Arity], _, Path, Reexport) ; true ). process_pce_import(op(P,T,N), Src, _, _) :- xref_push_op(Src, P, T, N). %% xref_public_list(+File, -Path, -Public, +Src) % % Find File as referenced from Src. Unify Path with the an % absolute path to the referenced source and Public with the % export list of that (module) file. Exports are produced by the % :- module/2 directive and all subsequent :- reexport directives. xref_public_list(File, Path, Public, Src) :- xref_public_list(File, Path, Src, Public, []). xref_public_list(File, Path, Src, Public, Rest) :- xref_source_file(File, Path, Src), prolog_open_source(Path, Fd), % skips possible #! line call_cleanup(read_public(Fd, Src, Public, Rest), prolog_close_source(Fd)). read_public(In, File, Public, Rest) :- read(In, (:- module(_, Export))), append(Export, Reexport, Public), read(In, ReexportDecl), read_reexport(ReexportDecl, In, File, Reexport, Rest). read_reexport((:- reexport(Spec)), In, File, Reexport, Rest) :- !, reexport_files(Spec, File, Reexport, Rest0), read(In, ReexportDecl), read_reexport(ReexportDecl, In, File, Rest0, Rest). read_reexport((:- reexport(Spec, Import)), In, File, Reexport, Rest) :- !, public_from_import(Import, Spec, File, Reexport, Rest0), read(In, ReexportDecl), read_reexport(ReexportDecl, In, File, Rest0, Rest). read_reexport(_, _, _, Rest, Rest). reexport_files([], _, Public, Public) :- !. reexport_files([H|T], Src, Public, Rest) :- !, xref_public_list(H, _, Src, Public, Rest0), reexport_files(T, Src, Rest0, Rest). reexport_files(Spec, Src, Public, Rest) :- xref_public_list(Spec, Src, Public, Rest). public_from_import(except(Map), File, Src, Export, Rest) :- !, xref_public_list(File, _, Public, Src), except(Map, Public, Export, Rest). public_from_import(Import, _, _, Export, Rest) :- import_name_map(Import, Export, Rest). except([], Public, Export, Rest) :- append(Public, Rest, Export). except([PI0 as NewName|Map], Public, Export, Rest) :- !, canonical_pi(PI0, PI), map_as(Public, PI, NewName, Public2), except(Map, Public2, Export, Rest). except([PI0|Map], Public, Export, Rest) :- canonical_pi(PI0, PI), select(PI2, Public, Public2), same_pi(PI, PI2), !, except(Map, Public2, Export, Rest). map_as([PI|T], Repl, As, [PI2|T]) :- same_pi(Repl, PI), !, pi_as(PI, As, PI2). map_as([H|T0], Repl, As, [H|T]) :- map_as(T0, Repl, As, T). pi_as(_/Arity, Name, Name/Arity). pi_as(_//Arity, Name, Name//Arity). import_name_map([], L, L). import_name_map([_/Arity as NewName|T0], [NewName/Arity|T], Tail) :- !, import_name_map(T0, T, Tail). import_name_map([_//Arity as NewName|T0], [NewName//Arity|T], Tail) :- !, import_name_map(T0, T, Tail). import_name_map([H|T0], [H|T], Tail) :- import_name_map(T0, T, Tail). canonical_pi(Name//Arity0, PI) :- integer(Arity0), !, PI = Name/Arity, Arity is Arity0 + 2. canonical_pi(PI, PI). same_pi(Canonical, PI2) :- canonical_pi(PI2, Canonical). /******************************* * INCLUDE * *******************************/ process_include([], _) :- !. process_include([H|T], Src) :- !, process_include(H, Src), process_include(T, Src). process_include(File, Src) :- catch(read_src_to_terms(File, Src, Terms), _, fail), !, process_terms(Terms, Src). process_include(_, _). process_terms([], _). process_terms([H|T], Src) :- process(H, Src), process_terms(T, Src). read_src_to_terms(File, Src, Terms) :- xref_source_file(File, Path, Src), prolog_open_source(Path, Fd), call_cleanup(read_clauses(Fd, Terms), prolog_close_source(Fd)). read_clauses(In, Terms) :- read_clause(In, C0), read_clauses(C0, In, Terms). read_clauses(end_of_file, _, []) :- !. read_clauses(Term, In, [Term|T]) :- read_clause(In, C), read_clauses(C, In, T). %% process_foreign(+Spec, +Src) % % Process a load_foreign_library/1 call. process_foreign(Spec, Src) :- current_foreign_library(Spec, Defined), ( xmodule(Module, Src) -> true ; Module = user ), process_foreign_defined(Defined, Module, Src). process_foreign_defined([], _, _). process_foreign_defined([H|T], M, Src) :- ( H = M:Head -> assert_foreign(Src, Head) ; assert_foreign(Src, H) ), process_foreign_defined(T, M, Src). /******************************* * CHR SUPPORT * *******************************/ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This part of the file supports CHR. Our choice is between making special hooks to make CHR expansion work and then handle the (complex) expanded code or process the CHR source directly. The latter looks simpler, though I don't like the idea of adding support for libraries to this module. A file is supposed to be a CHR file if it uses a use_module(library(chr) or contains a :- constraint/1 directive. As an extra bonus we get the source-locations right :-) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ chr_expandable((:- constraints(_))). chr_expandable((constraints(_))). chr_expandable((handler(_))) :- is_chr_file. chr_expandable((rules(_))) :- is_chr_file. chr_expandable(<=>(_, _)) :- is_chr_file. chr_expandable(@(_, _)) :- is_chr_file. chr_expandable(==>(_, _)) :- is_chr_file. chr_expandable(pragma(_, _)) :- is_chr_file. chr_expandable(option(_, _)) :- is_chr_file. is_chr_file :- source(Src, _), mode(chr, Src), !. process_chr(@(_Name, Rule), Src) :- process_chr(Rule, Src). process_chr(pragma(Rule, _Pragma), Src) :- process_chr(Rule, Src). process_chr(<=>(Head, Body), Src) :- chr_head(Head, Src, H), chr_body(Body, H, Src). process_chr(==>(Head, Body), Src) :- chr_head(Head, H, Src), chr_body(Body, H, Src). process_chr((:- constraints(C)), Src) :- process_chr(constraints(C), Src). process_chr(constraints(_), Src) :- ( mode(chr, Src) -> true ; assert(mode(chr, Src)) ). chr_head(X, _, _) :- var(X), !. % Illegal. Warn? chr_head(\(A,B), Src, H) :- chr_head(A, Src, H), process_body(B, H, Src). chr_head((H0,B), Src, H) :- chr_defined(H0, Src, H), process_body(B, H, Src). chr_head(H0, Src, H) :- chr_defined(H0, Src, H). chr_defined(X, _, _) :- var(X), !. chr_defined(#(C,_Id), Src, C) :- !, assert_constraint(Src, C). chr_defined(A, Src, A) :- assert_constraint(Src, A). chr_body(X, From, Src) :- var(X), !, process_body(X, From, Src). chr_body('|'(Guard, Goals), H, Src) :- !, chr_body(Guard, H, Src), chr_body(Goals, H, Src). chr_body(G, From, Src) :- process_body(G, From, Src). assert_constraint(_, Head) :- var(Head), !. assert_constraint(Src, Head) :- constraint(Head, Src, _), !. assert_constraint(Src, Head) :- functor(Head, Name, Arity), functor(Term, Name, Arity), flag(xref_src_line, Line, Line), assert(constraint(Term, Src, Line)). /******************************** * PHASE 1 ASSERTIONS * ********************************/ %% assert_called(+Src, +From, +Head) % % Assert the fact that Head is called by From in Src. We do not % assert called system predicates. assert_called(_, _, Var) :- var(Var), !. assert_called(Src, From, Goal) :- var(From), !, assert_called(Src, '', Goal). assert_called(_, _, Goal) :- hide_called(Goal), !. assert_called(Src, Origin, M:G) :- !, ( atom(M), callable(G) -> ( xmodule(M, Src) -> assert_called(Src, Origin, G) ; called(M:G, Src, Origin) -> true ; generalise(Origin, OTerm), generalise(G, GTerm), assert(called(M:GTerm, Src, OTerm)) ) ; true % call to variable module ). assert_called(_, _, Goal) :- system_predicate(Goal), !. assert_called(Src, Origin, Goal) :- called(Goal, Src, Origin), !. assert_called(Src, Origin, Goal) :- generalise(Origin, OTerm), generalise(Goal, Term), assert(called(Term, Src, OTerm)). %% hide_called(:Callable) % % Goals that should not turn up as being called. Hack. Eventually % we should deal with that using an XPCE plugin. hide_called(pce_principal:send_implementation(_, _, _)). hide_called(pce_principal:get_implementation(_, _, _, _)). hide_called(pce_principal:pce_lazy_get_method(_,_,_)). hide_called(pce_principal:pce_lazy_send_method(_,_,_)). assert_defined(Src, Goal) :- defined(Goal, Src, _), !. assert_defined(Src, Goal) :- generalise(Goal, Term), flag(xref_src_line, Line, Line), assert(defined(Term, Src, Line)). assert_foreign(Src, Goal) :- foreign(Goal, Src, _), !. assert_foreign(Src, Goal) :- generalise(Goal, Term), flag(xref_src_line, Line, Line), assert(foreign(Term, Src, Line)). %% assert_import(+Src, +Import, +PublicList, +From, +Reexport) is det. % % Asserts imports into Src. Import is the import specification, % PublicList is the list of known public predicates or unbound if % this need not be checked and From is the file from which the % public predicates come. If Reexport is =true=, re-export the % imported predicates. % % @tbd Tighter type-checking on Import. assert_import(_, [], _, _, _) :- !. assert_import(Src, [H|T], Public, From, Reexport) :- !, assert_import(Src, H, Public, From, Reexport), assert_import(Src, T, Public, From, Reexport). assert_import(Src, except(Except), Public, From, Reexport) :- !, is_list(Public), !, except(Except, Public, Import, []), assert_import(Src, Import, _All, From, Reexport). assert_import(Src, Import as Name, Public, From, Reexport) :- !, pi_to_head(Import, Term0), functor(Term0, _OldName, Arity), functor(Term, Name, Arity), ( in_public_list(Term0, Public) -> assert(imported(Term, Src, From)), assert_reexport(Reexport, Src, Term) ; flag(xref_src_line, Line, Line), assert_called(Src, ''(Line), Term0) ). assert_import(Src, Import, Public, From, Reexport) :- pi_to_head(Import, Term), !, ( in_public_list(Term, Public) -> assert(imported(Term, Src, From)), assert_reexport(Reexport, Src, Term) ; flag(xref_src_line, Line, Line), assert_called(Src, ''(Line), Term) ). assert_import(Src, op(P,T,N), _, _, _) :- xref_push_op(Src, P,T,N). in_public_list(_Head, Public) :- var(Public), !. in_public_list(Head, Public) :- member(Export, Public), pi_to_head(Export, Head). assert_reexport(false, _, _) :- !. assert_reexport(true, Src, Term) :- assert(exported(Term, Src)). %% assert_op(+Src, +Op) is det. % % @param Op Ground term op(Priority, Type, Name). assert_op(Src, op(P,T,_:N)) :- ( xop(Src, op(P,T,N)) -> true ; assert(xop(Src, op(P,T,N))) ). %% assert_module(+Src, +Module) % % Assert we are loading code into Module. This is also used to % exploit local term-expansion and other rules. assert_module(Src, Module) :- xmodule(Module, Src), !. assert_module(Src, Module) :- '$set_source_module'(_, Module), assert(xmodule(Module, Src)). assert_export(_, []) :- !. assert_export(Src, [H|T]) :- !, assert_export(Src, H), assert_export(Src, T). assert_export(Src, PI) :- pi_to_head(PI, Term), !, assert(exported(Term, Src)). assert_export(Src, op(P, A, N)) :- xref_push_op(Src, P, A, N). assert_dynamic(Src, (A, B)) :- !, assert_dynamic(Src, A), assert_dynamic(Src, B). assert_dynamic(_, _M:_Name/_Arity) :- !. % not local assert_dynamic(Src, PI) :- pi_to_head(PI, Term), ( thread_local(Term, Src, _) % dynamic after thread_local has -> true % no effect ; flag(xref_src_line, Line, Line), assert(dynamic(Term, Src, Line)) ). assert_thread_local(Src, (A, B)) :- !, assert_thread_local(Src, A), assert_thread_local(Src, B). assert_thread_local(_, _M:_Name/_Arity) :- !. % not local assert_thread_local(Src, PI) :- pi_to_head(PI, Term), flag(xref_src_line, Line, Line), assert(thread_local(Term, Src, Line)). assert_multifile(Src, (A, B)) :- !, assert_multifile(Src, A), assert_multifile(Src, B). assert_multifile(_, _M:_Name/_Arity) :- !. % not local assert_multifile(Src, PI) :- pi_to_head(PI, Term), flag(xref_src_line, Line, Line), assert(multifile(Term, Src, Line)). %% pi_to_head(+PI, -Head) is semidet. % % Translate Name/Arity or Name//Arity to a callable term. Fails if % PI is not a predicate indicator. pi_to_head(Var, _) :- var(Var), !, fail. pi_to_head(Name/Arity, Term) :- functor(Term, Name, Arity). pi_to_head(Name//DCGArity, Term) :- Arity is DCGArity+2, functor(Term, Name, Arity). assert_used_class(Src, Name) :- used_class(Name, Src), !. assert_used_class(Src, Name) :- assert(used_class(Name, Src)). assert_defined_class(Src, Name, _Meta, _Super, _) :- defined_class(Name, _, _, Src, _), !. assert_defined_class(_, _, _, -, _) :- !. % :- pce_extend_class assert_defined_class(Src, Name, Meta, Super, Summary) :- flag(xref_src_line, Line, Line), ( Summary == @(default) -> Atom = '' ; is_list(Summary) -> atom_codes(Atom, Summary) ; string(Summary) -> atom_concat(Summary, '', Atom) ), assert(defined_class(Name, Super, Atom, Src, Line)), ( Meta = @(_) -> true ; assert_used_class(Src, Meta) ), assert_used_class(Src, Super). assert_defined_class(Src, Name, imported_from(_File)) :- defined_class(Name, _, _, Src, _), !. assert_defined_class(Src, Name, imported_from(File)) :- assert(defined_class(Name, _, '', Src, file(File))). /******************************** * UTILITIES * ********************************/ %% generalise(+Callable, -General) % % Generalise a callable term. generalise(Var, Var) :- var(Var), !. % error? generalise(pce_principal:send_implementation(Id, _, _), pce_principal:send_implementation(Id, _, _)) :- atom(Id), !. generalise(pce_principal:get_implementation(Id, _, _, _), pce_principal:get_implementation(Id, _, _, _)) :- atom(Id), !. generalise(''(Line), ''(Line)) :- !. generalise(Module:Goal0, Module:Goal) :- atom(Module), !, generalise(Goal0, Goal). generalise(Term0, Term) :- callable(Term0), functor(Term0, Name, Arity), functor(Term, Name, Arity). /******************************* * SOURCE MANAGEMENT * *******************************/ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This section of the file contains hookable predicates to reason about sources. The built-in code here can only deal with files. The XPCE library(pce_prolog_xref) provides hooks to deal with XPCE objects, so we can do cross-referencing on PceEmacs edit buffers. Other examples for hooking can be databases, (HTTP) URIs, etc. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ :- multifile prolog:xref_source_directory/2. % +Source, -Dir %% xref_source_file(+Spec, -File, +Src) is semidet. %% xref_source_file(+Spec, -File, +Src, +Options) is semidet. % % Find named source file from Spec, relative to Src. xref_source_file(Plain, File, Source) :- xref_source_file(Plain, File, Source, []). xref_source_file(Plain, File, Source, Options) :- atom(Plain), \+ is_absolute_file_name(Plain), ( prolog:xref_source_directory(Source, Dir) -> true ; atom(Source), file_directory_name(Source, Dir) ), atomic_list_concat([Dir, /, Plain], Spec), do_xref_source_file(Spec, File, Options), !. xref_source_file(Spec, File, _, Options) :- do_xref_source_file(Spec, File, Options), !. xref_source_file(Spec, _, _, _) :- verbose, print_message(warning, error(existence_error(file, Spec), _)), fail. do_xref_source_file(Spec, File, Options) :- option(file_type(Type), Options, prolog), absolute_file_name(Spec, [ file_type(Type), access(read), file_errors(fail) ], File), !.