%% ------------------------------------------------------------
%%
%% Ciao/Prolog library browser utility
%%
%% AUTHOR:   Angel Fernandez Pineda
%% DATE:     Abril 1999
%%
%% This utility is intended to be used at CiaoSHELL toplevel.
%% ------------------------------------------------------------

:- module(librowser,
	[
	    update/0,
	    browse/2,
	    where/1,
	    describe/1,
	    system_lib/1,
	    apropos/1
	],[assertions]).

%% ------------------------------------------------------------

:- use_module(library(filenames)).
:- use_module(library(read)).
:- use_module(library(fastrw)).
:- use_module(library(system)).
:- use_module(library(streams)).
:- use_module(library(patterns)).
:- use_module(library(lists),[append/3]).

%% ------------------------------------------------------------

:- data exports/3.
:- data lastm/1.

%% ------------------------------------------------------------

:- comment(author, "Angel Fernandez Pineda" ).

:- comment(title,
	"The Ciao library browser").

:- comment(subtitle,
	"A tool for Ciao Prolog toplevel shell").

:- comment(summary,
	"Librowser was designed to provide a simple help facility
         to the user when messing around with Ciao toplevel.

         It will search the default library paths looking for
         @tt{.itf interface files}. Those interface files are automatically
         generated by Ciao when compiling libraries.
        ").

:- comment(module,
	"@lib{librowser} library provides a set of predicates wich enables
	the user to interactively find Ciao/Prolog libraries and/or any
	predicate exported by them.

        This is a simple example:

        @begin{verbatim}
?- apropos('*find*').
persdbrt_sql: dbfindall/4
persdbrtsql: dbfindall/4
conc_aggregates: findall/3
linda: rd_findall/3
vndict: find_name/4
internals: $find_file/8
aggregates: findall/4,findall/3

yes
?-
@end{verbatim}

        Librowser is specially usefull when using inside GNU Emacs, just
        place the cursor over a librowser response and press C-cTAB in order
        to get help on the related predicate.
        Refer to the @bf{\"Using Ciao inside GNU Emacs\"} chapter for further
        information.
        ").

:- comment(usage,
	"It is not necesary to use this library at user programs.
         It was designed to be used at the Ciao @em{toplevel}
	 shell: @apl{ciaosh}. In order to do so, just make use of
	 @pred{use_module/1} as follows:

         @tt{use_module(library(librowser))}.

         Then, the library interface must be read. This is
         automatically done when calling any predicate at librowser,
         and the entire process will take a little moment.So, you
         should want to perform such a process after loading
         the Ciao toplevel:

         @begin{verbatim}
Ciao 0.9 #75: Fri Apr 30 19:04:24 MEST 1999
?- use_module(library(librowser)).

yes
?- update.

@end{verbatim}

        Whether you want this process to be automatically performed when
        loading @apl{ciaosh}, you may include those lines in your
        @em{.ciaorc} personal initialization file.
        ").

%% ------------------------------------------------------------
%%
%% SOME TYPES AND PROPERTIES FOR DOCUMENTATION PORPOUSES
%%
%% ------------------------------------------------------------

:- prop module_name(Module) #
	"@var{Module} is a module name (an atom)".

module_name(_).

:- prop pred_spec(Spec) #
	"@var{Spec} is a @bf{Functor/Arity} predicate
         specification".

pred_spec(_).

:- comment(apropos_spec/1,
	"Defined as:
          @includedef{apropos_spec/1}
        ").

:- prop apropos_spec(S) + regtype #
	"@var{S} is a Pattern/Arity specification.".

apropos_spec(_).


apropos_spec(Pattern/Arity) :-
	pattern(Pattern),
	int(Arity).

:- comment(doinclude,apropos_spec/1).

%% ------------------------------------------------------------
%%
%% READ LIBRARY INFO
%%
%% ------------------------------------------------------------

:- comment(update/0,
	"This predicate will scan the Ciao @concept{system libraries} for
	 predicate definitions. This may be done once time before
	 calling any other predicate at this library.

         update/0 will also be automatically called (once) when
         calling any other predicate at librowser.").

:- pred update #
	"Creates an internal database of modules at Ciao
	 @concept{system libraries}.".


update :-
	retractall_fact(exports(_,_,_)),
	inform_user(['Reading Ciao library info, please wait...']),
	fail.

update :-
	ciaolibdir(CiaoPath),
	atom_concat(CiaoPath,'/lib/',LIB),
	atom_concat(CiaoPath,'/library/',LIBRARY),
	related_files_at(LIB,LIB_itf),
	intercept(extract_info_from(LIB_itf),_,true),
	related_files_at(LIBRARY,LIBRARY_itf),
	intercept(extract_info_from(LIBRARY_itf),_,true),
	fail.

update :-
	inform_user(['Browser has been loaded...']),
	nl.

update_when_needed :-
	exports(_,_,_),
	!.

update_when_needed :-
	update.

%% ------------------------------------------------------------

related_files_at(Dir,Files) :-
	directory_files(Dir,AllFiles),
	related_files_at_aux(AllFiles,Dir,Files).


related_files_at_aux([],_,[]).

related_files_at_aux(['.'|Nf],Dir,NNf) :-
	!,
	related_files_at_aux(Nf,Dir,NNf).

related_files_at_aux(['..'|Nf],Dir,NNf) :-
	!,
	related_files_at_aux(Nf,Dir,NNf).

related_files_at_aux([File|Nf],Dir,NNf) :-
	atom_concat('.#',_,File),
	!,
	related_files_at_aux(Nf,Dir,NNf).

related_files_at_aux([File|Nf],Dir,[itf(AbsFile,Module)|NNf]) :-
	atom_concat(Module,'.itf',File),
	!,
	atom_concat(Dir,File,AbsFile),
	related_files_at_aux(Nf,Dir,NNf).

related_files_at_aux([File|Nf],Dir,RelatedFiles) :-
	atom_concat(Dir,File,AbsFile),
	file_property(AbsFile,type(directory)),
	!,
	atom_concat(AbsFile,'/',NewDir),
	related_files_at_aux(Nf,Dir,NNf),
	related_files_at(NewDir,NewFiles),
	append(NNf,NewFiles,RelatedFiles).

related_files_at_aux([_|Nf],Dir,NNf) :-
	!,
	related_files_at_aux(Nf,Dir,NNf).

%% ------------------------------------------------------------

extract_info_from([]).

extract_info_from([itf(File,Mod)|Nf]) :-
	open_input(File,(Old,Strm)),
        set_input(Strm),
	inform_user(['{Reading interface info from ',Mod,'}']),
	read_exports(Mod),
	close(Strm),
        set_input(Old),
	extract_info_from(Nf).

%% ------------------------------------------------------------

read_exports(_Module) :-
        read_term(_CiaoItfSignature,[]),
        fail.

read_exports(Module) :-
	getterm(Term),
	Term = e(F,A,_,_),
	asserta_fact(exports(Module,F,A)),
	fail.

read_exports(_).

getterm(Term) :-
	( fast_read(T) -> Term = T ; Term = end_of_file ),
	( Term \== end_of_file -> true ; (!,fail) ).

getterm(Term) :-
	getterm(Term).

%% ------------------------------------------------------------
%%
%% BROWSE PREDICATE
%%
%% ------------------------------------------------------------

:- comment(browse/2,
	"This predicate is fully reversible, and is provided to
	 inspect concrete predicate specifications.
         For example:
         @begin{verbatim}
?- browse(M,findall/A).

A = 3,
M = conc_aggregates ? ;

A = 4,
M = aggregates ? ;

A = 3,
M = aggregates ? ;

no
?-
@end{verbatim}
        ").

:- pred browse(Module,Spec):
	( module_name(Module) , pred_spec(Spec) ) #
	"Asocciates the given @var{Spec} predicate specification
	 with the @var{Module} which exports it.".

browse(M,F/A) :-
	update_when_needed,
	exports(M,F,A).

%% ------------------------------------------------------------

:- comment(where/1,
	"This predicate will print at the screen the module
         needed in order to import a given predicate specification.
         For example:
@begin{verbatim}
?- where(findall/A).
findall/3 exported at module conc_aggregates
findall/4 exported at module aggregates
findall/3 exported at module aggregates

yes
?-
@end{verbatim}
        ").

:- pred where(Spec): pred_spec #
	"Display what module to load in order to import
	 the given @var{Spec}.".

where(F/A) :-
	exports(Module,F,A),
	inform_user([F,'/',A,' exported at module ',Module]),
	fail.

where(_).

%% ------------------------------------------------------------

:- comment(system_lib/1,
	"It retrieves on backtracking all Ciao system libraries stored in
         the internal database. Certainly,
	 those which were scanned at @pred{update/0} calling.
        ").

:- pred system_lib(Module) : module_name #
	"@var{Module} variable will be successively instantiated
         to the system libaries stored in the internal database.".

system_lib(Module) :-
	update_when_needed,
	exports(Module,_,_).

%% ------------------------------------------------------------

:- comment(describe/1,
	"This one is used to find out which predicates were exported
         by a given module. Very usefull when you know the library,
         but not the concrete predicate. For example:
@begin{verbatim}
?- describe(librowser).
Predicates at library librowser :

apropos/1
system_lib/1
describe/1
where/1
browse/2
update/0

yes
?-
@end{verbatim}
        ").

:- pred describe(Module) : module_name #
	"Display a list of exported predicates at the given @var{Module}".

describe(Module) :-
	update_when_needed,
	atom(Module),
	inform_user(['Predicates at library ',Module,' : ']),
	nl,
	exports(Module,F,A),
	inform_user([F,'/',A,' ']),
	fail.

describe(_).

%% ------------------------------------------------------------

:- comment(apropos/1,
	"This tool makes use of @concept{regular expresions} in order
	 to find predicate specifications. It is very usefull whether
         you can't remember the full name of a predicate.
         Regular expresions take the same format as described in
         library @lib{patterns}. Example:
@begin{verbatim}
?- apropos('atom_*').

terms: atom_concat/2
concurrency: atom_lock_state/2
atomic_basic: atom_concat/3,atom_length/2,atom_codes/2
iso_byte_char: atom_chars/2

yes
?-
@end{verbatim}

").

:- pred apropos(RegSpec): apropos_spec #
	"This will search any predicate specification @var{Spec} which
	 matches the given @var{RegSpec} incomplete predicate specification.
        ".


apropos(_) :-
	update_when_needed,
	fail.

apropos(Root) :-
	atom(Root),
	!,
	apropos(Root/_).

apropos(Root/Arity) :-
	atom(Root),
	set_fact(lastm('$nothing$')),
	!,
	apropos_aux(Root,Arity).

apropos_aux(Root,A) :-
	atom_codes(Root,RootCodes),
	exports(Module,F,A),
	atom_codes(F,FCodes),
	match_pattern(RootCodes,FCodes),
	( lastm(Module) ->
	    ( display(','),display(F),display('/'),display(A))
	  ;
	  (
	      set_fact(lastm(Module)),
	      nl,
	      display(Module),
	      display(': '),
	      display(F),
	      display('/'),
	      display(A)
	  )
	),
	fail.

apropos_aux(_,_) :-
	nl.

%% ------------------------------------------------------------

%  :- initialization(update).

%% ------------------------------------------------------------

:- comment(version_maintenance,dir('../../version')).

:- comment(version(1*7+21,2000/09/26,13:37*17+'CEST'), " Implementation
   updated due to changes on itf files format, now using fastrw
   procedures.  (Angel Fernandez Pineda)").

:- comment(version(1*3+39,1999/07/21,12:08*04+'MEST'), "Fixed apropos/1,it
   did not work due to changes on the patterns library.  (Angel Fernandez
   Pineda)").

:- comment(version(0*9+89,1999/05/10,21:10*14+'MEST'), "Changed call to
   match_pattern_pred/2 to match_pattern/2.  (Daniel Cabeza Gras)").

:- comment(version(0*9+77,1999/05/03,19:10*27+'MEST'), "Fixed bug which
   tried to read NFS temporary lock files.  (Angel Fernandez Pineda)").

:- comment(version(0*9+76,1999/05/03,18:52*33+'MEST'), "All remaining bugs
   has been removed: librowser will work on Win32 systems, update/0 is not
   needed to be called manually anymore.  (Angel Fernandez Pineda)").

:- comment(version(0*1+1,1999/03/01,16:56*12+'MET'), "Fixed little
   documentation bugs (Angel Fernandez Pineda)").

:- comment(version(0*1+0,1999/02/23,19:17*23+'MET'), "Documentation first
   created (Angel Fernandez Pineda)").
