; $Id: resolve_all.pro,v 1.23 2004/01/21 15:55:01 scottm Exp $
;
; Copyright (c) 1995-2004, Research Systems, Inc.  All rights reserved.
;	Unauthorized reproduction prohibited.
;+
; NAME:
;	RESOLVE_ALL
;
; PURPOSE:
;	Resolve (by compiling) all procedures and functions.
;	This is useful when preparing .sav files containing all the IDL
;	routines required for an application.
; CATEGORY:
;	Programming.
; CALLING SEQUENCE:
;	RESOLVE_ALL
; INPUTS:
;	None.
; KEYWORD PARAMETERS:
;	CLASS = if set, a list of object class names. RESOLVE_ALL's rules
;	    for finding uncompiled functions and procedures are not able to
;	    find object definitions or methods, because those things are not
;	    known to IDL until the object classes are actually instantiated
;	    and the methods called. However, if CLASS is set, RESOLVE_ALL
;	    will ensure that the __DEFINE files for those classes and their
;	    superclasses are compiled and execute. If then locates all methods
;	    for those classes and their superclasses and makes sure they are
;	    also compiled.
;	CONTINUE_ON_ERROR = if set, continue when a routine fails to
;	    resolve, otherwise throw an error and stop.
;	QUIET = if set, produce no messages.
;	RESOLVE_EITHER = A scalar or array of routine names to resolve.
;	    Use this keyword instead of RESOLVE_FUNCTION or RESOLVE_PROCEDURE
;	    if you do not know the type of the routine being resolved.
;	    If the routines are already compiled, they are not recompiled.
;	RESOLVE_FUNCTION = a scalar or array of function names to resolve.
;	    If the routines are already compiled, they are not recompiled.
;	RESOLVE_PROCEDURE = a scalar or array of procedure names to resolve.
;	    If the routines are already compiled, they are not recompiled.
; 	SKIP_ROUTINES = an optional string array containing the names
; 	    of routines to NOT resolve.  This is useful when a library
; 	    file containing the designated routines will be later included.
;	UNRESOLVED = if CONTINUE_ON_ERROR is set, this output parameter will
;	    contain the names of the unresolved procedures and functions
;	    in a string array.  Routines in the SKIP_ROUTINES list are
;	    also included in this result.
; OUTPUTS:
;	No explicit outputs.
; COMMON BLOCKS:
;	None.
; SIDE EFFECTS:
; RESTRICTIONS:
;	Will not resolve procedures or functions that are called via
;	CALL_PROCEDURE, CALL_FUNCTION, or EXECUTE, or object methods.
;	Only explicit calls are resolved.
;
;	If an unresolved procedure or function is not in the IDL
;	search path, an error occurs, and no additional routines
;	are resolved unless CONTINUE_ON_ERROR is specified.
;
;	This routine does not support the idea of a function and procedure
;	both having the same name, and does not handle that case. This is
;	generally not a good idea anyway, as it is confusing.
;
; PROCEDURE:
;	This routine iteratively determines the names of unresolved calls
;	to user-written or library procedures and functions, and then
;	compiles them.  The process stops when there are no unresolved
;	routines. If the CLASS keyword is set, this routine first ensures
;	that all the class definitions are compiled (from the __define.pro)
;	file, and that all methods for those classes and their superclasses
;	are compiled.
;
; EXAMPLE:
;	RESOLVE_ALL.
;
; MODIFICATION HISTORY:
; 	Written by:
;	DMS, RSI, January, 1995.
;	DMS, RSI, April, 1997, Added SKIP_ROUTINES keyword.
;	AB, RSI, April 1998, Added CONTINUE_ON_ERROR keyword. Reorganized
;		the body of the resolving code.
;	DMS, Aug, 1998.  Added UNRESOLVED keyword.
;	AB, 13 January 1999, Added RESOLVE_EITHER keyword. Removed the old
;		restriction that only one of the RESOLVE_ keywords are
;		processed in a single call.
;	AB, 6 February 2003, Added CLASS keyword.
;-

pro resolve_all_class, class, quiet, cont, skipr
  ; Resolve the specified classes. This means that the __DEFINE procedures
  ; have been executed for all classes and their superclasses, and that
  ; all methods available from the current directory and !PATH for those
  ; classes and superclasses have been compiled.
  ;
  ; entry:
  ;	quiet - Quiet keyword from main routine.
  ;	cont - TRUE if should keep going when a routine fails to resolve,
  ;	    FALSE to throw an error and stop.
  ;	do_func - TRUE to process functions, FALSE to process procedures.
  ;	skipr - Uppercase names of routines to skip resolving. This argument
  ;		can be undefined, scalar, or array.
  ;
  ; exit:
  ;	The classes and methods have been resolved if possible.
  ;	The natural action of the main RESOLVE_ALL body will handle
  ;     resolving any functions or procedures that are called by the
  ;     code resolved here.


  COMPILE_OPT hidden
  on_error, 2		; Return to caller if error not caught


  num_skipr = n_elements(skipr)


  ; Get the character used to separate directories in a path, and the
  ; character used to separate paths in a search path.
  psep = path_sep()
  spsep = path_sep(/SEARCH_PATH)

  ; Force every specified class to have its structure defined by instantiating
  ; a copy. This causes IDL to locate and compile any necessary __DEFINE files,
  ; for the classes, and any of their superclasses. After each instantiation,
  ; query for the names of any superclasses, and add these names to a local
  ; copy of the classes we started with. The end result will be a list of
  ; all of the requested classes, and all of the superclasses they use.
  ;
  ; Once this is done, all the classes and their superclasses will be defined,
  ; as will any methods contained in their __DEFINE file. Missing will be
  ; any methods defined in a separate file.
  l_class = class
  for i=0, n_elements(class)-1 do begin
    catch, error
    if error eq 0 then begin
      if (~ execute('a={' + class[i] + '}')) then goto, bad_class
      if (~ quiet) then MESSAGE,/INFORMATIONAL,/NONAME, $
	  'Resolved class structure: ' + strupcase(class[i])
      super = obj_class(class[i], /superclass, count=count)
      if (count ne 0) then l_class = [l_class, super ]
    endif else begin
      ; Object class structure definition not found.
      ; If we're supposed to halt on error, then reissue it so that we jump
      ;
      ; Otherwise, then we continue on error. Take it out of the resolve
      ; list, and let the user see the error. Add it to the skip list so
      ; that the rest of RESOLVE_ALL won't keep trying to resolve it.
      ;
    bad_class:
    catch, /cancel
      if (~ cont) then MESSAGE, /REISSUE_LAST $		; Will usually longjmp
      else if (~ quiet) then HELP, /LAST_MESSAGE
      l_class[i] = ''			; Don't resolve its methods
      s = strupcase(class[i] + '__DEFINE')
      skipr = num_skipr gt 0 ? [skipr, s] : [s];
      num_skipr = num_skipr + 1
    endelse

  endfor
  catch, /cancel

  ; Force all the names to lowercase. This is necessary on operating systems
  ; with case sensitive file names (Unix) and harmless elsewhere. Then,
  ; eliminate any duplicates so that we only try to resolve the methods for
  ; each class once.
  ;
  l_class = strlowcase(l_class)
  l_class = l_class[uniq(l_class, sort(l_class))]

  ; Search for all files in the current directory or referenced via
  ; !PATH, and build a list of the routine names those files are expected
  ; to contain. Given that list, RESOLVE_ROUTINE will be used to force their
  ; compilation, finishing the process of resolving the class.
  path_elts = [ '.', strtok(!path, spsep, /EXTRACT) ]
  resolve = ''
  for i=0, n_elements(l_class)-1 do begin
    a = file_search(path_elts + psep + l_class[i] + '__*.pro', $
	count=count)
    if (count ne 0) then resolve = [resolve, a ]
    a = file_search(path_elts + psep + l_class[i] + '__*.sav', $
	count=count)
    if (count ne 0) then resolve = [resolve, a ]
    if (~ quiet) then MESSAGE,/INFORMATIONAL,/NONAME, $
	  'Identified method files: ' + strupcase(l_class[i])
  endfor

  ; Extract the routine name from the file name by tossing the directory
  ; part of the path, and the file extension, and converting __ to :: for
  ; methods.
  ;
  ; Use strpos to find the final directory delimiter and add one to it
  ; to reference the following character. If there is no directory delimiter,
  ; STRPOS returns -1, and adding 1 to that makes 0, which is the beginning
  ; of the name also.
  start = strpos(resolve, psep, /REVERSE_SEARCH) + 1
  len = strlen(resolve) - start - 4
  for i=0, n_elements(resolve)-1 do begin
    s = strupcase(strmid(resolve[i], start[i], len[i]))
    pos = STRPOS(s, '__', /REVERSE_SEARCH)
    if ((pos gt 0) and (strmid(s, pos) ne '__DEFINE')) then begin
        ; Make sure we don't have multiple underscores in our method name.
        while (STRMID(s, pos-1, 1) eq '_') do pos--
	    STRPUT, s, '::', pos
	endif
    resolve[i] = s
  endfor

  ; Remove skip routines from the unresolved list.
  if num_skipr gt 0 then begin
    ; Remove skip routines from the unresolved list.
    for i=0, n_elements(resolve)-1 do $
      if (total(resolve[i] eq skipr) ne 0) then resolve[i] = ''
  endif

  ; Use RESOLVE_ROUTINE to force the compilation of the routines we've
  ; identified. Do it one at a time so that we can control error handling
  ; and continuation.
  for i=0, n_elements(resolve)-1 do begin
    if (s = resolve[i]) then begin
      if (cont) then catch, error else error = 0
      if error eq 0 then begin
        RESOLVE_ROUTINE, s, /NO_RECOMPILE, /EITHER
      endif else begin
        ; In case this branch has a programming error, avoid infinite loop
	catch, /cancel

	; Current routine was not found. Let the user see the error.
	; Add it to the skip list so that the rest of RESOLVE_ALL won't
        ; keep trying to resolve it.
        skipr = num_skipr gt 0 ? [skipr, resolve[i]] : [resolve[i]]
        num_skipr = num_skipr + 1
	if quiet eq 0 then help,/last_message
      endelse
    endif
  endfor
end







function resolve_all_body, quiet, cont, do_func, skipr
;
; Resolve all unresolved procedures or functions if possible.
;
; entry:
;	quiet - Quiet keyword from main routine.
;	cont - TRUE if should keep going when a routine fails to resolve,
;	    FALSE to throw an error and stop.
;	do_func - TRUE to process functions, FALSE to process procedures.
;	skipr - Uppercase names of routines to skip resolving. This argument
;		can be undefined, scalar, or array.
;
; exit:
;	All routines of the specified type have been resolved if possible.
;	Returns the number of routines for which an attempt to resolve was
;	made.
;
;	If cont is TRUE, any routines that fail to resolve are concatenated
;	to skipr so that subsequent calls won't try again.

    COMPILE_OPT hidden
    on_error, 2		; Return to caller if error not caught

    n_unresolved = 0
    num_skipr = n_elements(skipr)
    a = ROUTINE_INFO(FUNCTIONS=do_func, /UNRESOLVED)
    if num_skipr gt 0 then begin
        ; Remove skip routines from the unresolved list.
        j = 0L
        for i=0, n_elements(a)-1 do if total(a[i] eq skipr) eq 0 then begin
            a[j] = a[i]
            j = j + 1
        endif
        if j gt 0 then a = a[0:j-1] else a = ''
    endif
    if strlen(a[0]) gt 0 then begin
	num = n_elements(a)
        n_unresolved = n_unresolved + num
        for i=0, num-1 do begin
	    name = a[i]
	    if (cont) then catch, error else error = 0
	    if error eq 0 then begin
	        resolve_routine, name, IS_FUNCTION=do_func
	    endif else begin
	        ; Current routine was not found. Let the user see the
	        ; error, and then add it to the skip list.
	        if quiet eq 0 then help,/last_message
		skipr = num_skipr gt 0 ? [skipr, name] : [name];
	        num_skipr = num_skipr + 1
	    endelse
        endfor
    endif

    return, n_unresolved
end







PRO resolve_all, CLASS=class, CONTINUE_ON_ERROR=cont, QUIET = quiet, $
	RESOLVE_EITHER=resolve_either, RESOLVE_FUNCTION=resolve_function, $
	RESOLVE_PROCEDURE=resolve_procedure, SKIP_ROUTINES=skip_routines, $
        UNRESOLVED=skipr

    on_error, 2		; Return to caller if error not caught

    if n_elements(quiet) ne 0 then begin
        quiet_save=!quiet
        !quiet = quiet
    endif else quiet = 0

    local_cont = keyword_set(cont)
    if n_elements(skip_routines) gt 0 then skipr = strupcase(skip_routines)

    ; If the CLASS keyword is present, resolve the classes. However,
    ; skip over empty strings.
    n_class = n_elements(class);
    if (n_class ne 0) then begin
	idx = where(strcompress(class, /REMOVE_ALL) ne '', c)
	if (c ne 0) then begin
            resolve_all_class, (c eq n_class) ? class : class[idx], $
		quiet, local_cont, skipr
	endif
    endif

    if keyword_set(resolve_either) then $
	resolve_routine, /NO_RECOMPILE, /EITHER, resolve_either

    if keyword_set(resolve_procedure) then $
	resolve_routine, /NO_RECOMPILE, resolve_procedure

    if keyword_set(resolve_function) then $
	resolve_routine, /NO_RECOMPILE, /IS_FUNCTION, resolve_procedure



    repeat begin
        cnt = 0
        cnt = cnt + resolve_all_body(quiet, local_cont, 0, skipr)
        cnt = cnt + resolve_all_body(quiet, local_cont, 1, skipr)
    endrep until cnt le 0

done:
    if n_elements(quiet_save) ne 0 then !quiet = quiet_save

end
