R/obj_find.r

Defines functions obj_find

Documented in obj_find

#' Find an object in the workspace including user-defined environments
#' 
#' Look for an object in the whole workspace including all environments defined within it
#' (possibly recursively) and return ALL the environment(s) where the object is found.
#' User-defined environments are also searched.
#' Note that both the "recursive search" and the "user-defined environments search" makes this function
#' quite different from functions \link{find} and \link{exists} of the base package.
#' Optionally, the search can be limited to a specified environment, as opposed to carrying it out in the whole workspace.
#' Still, all user-defined environments defined inside the specified environment are searched.
#' 
#' @param obj object to be searched given as the object itself or as a character string. If given as an object,
#' expressions are accepted (see details on how expressions are handled).
#' @param envir environment where the search for \code{obj} should be carried out.
#' It defaults to \code{NULL} which means \code{obj} is searched in the calling environment
#' (i.e. in the environment calling this function), unless \code{globalsearch=TRUE} in which case
#' it is searched in the whole workspace.
#' @param envmap data frame containing a lookup table with name-address pairs of environment names and
#' addresses to be used when searching for environment \code{env}. It defaults to \code{NULL} which means that the
#' lookup table is constructed on the fly with the environments defined in the \code{envir} environment
#' --if not \code{NULL}--, or in the whole workspace if \code{envir=NULL}.
#' See the details section for more information on its structure.
#' @param globalsearch when \code{envir=NULL} it specifies whether the search for \code{obj} should be done
#' globally, i.e. in the whole workspace, or just within the calling environment.
#' @param n non-negative integer indicating the number of levels to go up from the calling function environment
#' to evaluate \code{obj}. It defaults to 0 which implies that \code{obj} is evaluated in the environment
#' of the calling function (i.e. the function that calls \code{obj_find()}).
#' @param return_address whether to return the address of the environments where the object is found in addition
#' to their names.
#' @param include_functions whether to include funtion execution environments as environments where the object
#' is searched for. Set this flag to \code{TRUE} with caution because there may be several functions where the
#' same object is defined, for instance functions that are called as part of the object searching process!
#' @param silent run in silent mode? If not, the search history is shown,
#' listing all the environments that are searched for object \code{obj}. It defaults to TRUE.
#' 
#' @details
#' An object is found in an environment if it is reachable from within that environment. An object is considered
#' reachable in an environment if either one of the following occurs:
#' \itemize{
#' \item it exists in the given environment
#' \item it exists in a user-defined environment defined inside the given environment or in any environment
#' recursively defined inside them
#' }
#' 
#' Note that \code{obj_find} differs from base functions \code{find} and \code{exists} in that \code{obj_find}
#' searches for the object inside user-defined environments within any given environment in a \emph{recursive} way.
#' 
#' In particular, compared to:
#' \itemize{
#' \item{\code{find}:} \code{obj_find} searches for objects inside user-defined environments while \code{find} is not
#' able to do so (see examples).
#' \item{\code{exists}:} \code{obj_find} \emph{never} searches for objects in the parent environment of \code{envir}
#' when \code{envir} is not \code{NULL}, as is the case with the \code{exists} function when its \code{inherits}
#' parameter is set to \code{TRUE} (the default).
#' If it is wished to search for objects in parent environments, simply set \code{envir=NULL}
#' and \code{globalsearch=TRUE}, in which case the object will be searched in the whole workspace
#' and the environments where it is found will be returned.
#' }
#' 
#' When the object is found, an array containing the names of all the environments where the object is found is
#' returned.
#' 
#' When \code{envir} is not \code{NULL} attached packages are not included in the search for \code{obj},
#' unless of course \code{envir} is itself a package environment.
#' 
#' When given as an object, \code{obj} can be an expression. If the expression is an attribute of a list
#' or an array element, the object contained therein is searched for.
#' Ex: if \code{alist$var = "x"} then object \code{x} is searched.
#' 
#' If \code{envmap} is passed it should be a data frame providing an address-name pair lookup table
#' of environments and should contain at least the following columns:
#' \itemize{
#' \item{\code{location}} for user-defined environments, the name of the environment where the environment
#' is located; otherwise \code{NA}.
#' \item{\code{pathname}} the full \emph{environment path} to reach the environment separated by \code{$}
#' (e.g. \code{"env1$env$envx"})
#' \item{\code{address}} the 8-digit (32-bit architectures) thru 16-digit (64-bit architectures) memory address
#' of the environment given in \code{pathname} enclosed in < > (e.g. \code{"<0000000007DCFB38>"}
#' (64-bit architectures))
#' Be ware that Linux Debian distributions may have a 12-digit memory address representation.
#' So the best way to know is to check a memory address by calling e.g. `address("x")`.
#' }
#' Passing an \code{envmap} lookup table is useful for speedup purposes, in case several calls to this
#' function will be performed in the context of an unchanged set of defined environments.
#' Such \code{envmap} data frame can be created by calling \link{get_env_names}.
#' Use this parameter with care, as the matrix passed may not correspond to the actual mapping of existing
#' environments to their addresses and in that case results may be different from those expected.
#' 
#' @return The return value depends on the value of parameter \code{return_address}: when \code{FALSE}
#' (the default) it returns an array containing the names of the environments where the object \code{obj}
#' is found; when \code{TRUE} it returns a list with two attributes: \code{"env_full_names"} and
#' \code{"env_addresses"} with respectively the environment names and addresses where the object is found.
#' 
#' @examples 
#' # Define a variable in the global environment
#' x = 4
#' # Create new environments, some nested
#' env1 = new.env()
#' with(env1, envx <- new.env())
#' env1$x = 3
#' env1$envx$x = 2
#' env1$y = 5
#' 
#' # Look for objects (crawling environments recursively)
#' obj_find(x)                  # "env1" "env1$envx" "R_GlobalEnv"
#' obj_find("x")                # "env1" "env1$envx" "R_GlobalEnv"
#' obj_find("x", envir=env1)    # "env1" "envx" (as the search is limited to the env1 environment)
#' obj_find("y")                # "env1"
#' obj_find(nonexistent)        # NULL (note that NO error is raised even if the object does not exist)
obj_find = function(obj, envir=NULL, envmap=NULL, globalsearch=TRUE, n=0, return_address=FALSE, include_functions=FALSE, silent=TRUE) {

  # look_for_environment()
  # Function that looks for objects that are unnamed environments.
	# The function returns a named list with the updated env_full_names and env_addresses arrays.
  look_for_environment = function(obj, envmap, env_full_names, env_addresses) {
      # This function has been defined because when 'obj' is an unnamed environment (e.g. a function execution environment)
      # whose name returned by get_obj_name is "<environment>", we cannot look for the object by simply checking
      # in which environment it exists using the exists() function on the object's name.
      # The solution instead is to retrieve the object's address and then look for the address in the
      # environment map table 'envmap' (this is the only way we can get to the name of an unnamed environment!!
      # --i.e. through the address-name lookup table)
      # (in fact running get_obj_name() on an unnamed environment returns "<environment>" which is not useful at all!)
      obj_address = address(obj)

      # Look for this address in the 'envmap' lookup table and return either the "path" or the "location" value
      # (the latter is returned if "path" is empty)
      # In fact, we should not return the "pathname" as we do for regular (non-environment) objects,
      # since for environment objects the "pathname" will coincide with the environment name!
      # and what we need to know is the **environment where the object is found**... 
      ind = which(envmap[,"address"] == obj_address)
      
      # "Clean up" the matched environments:
      # i.e. in case both "function" and "proper" environments are matched, keep just the "proper" environments
      # (see more comments in the function called here)
      ind = clean_up_matching_environments(envmap, ind)
      
      if (length(ind) > 0) {
        # Construct the environment where the object (also an environment) is found by using either
        # the value of 'path' or of 'location': we use simply 'path' when 'path' is not empty, o.w.
        # we use 'location', but it's important to note that we do NOT use location$path when path
        # is not empty, because we don't want to show to the user for instance "R_GlobalEnv$env_of_envs"
        # because by showing simply "env_of_envs" we are implying that env_of_envs is located in the
        # global environment (o.w. the location would be indicated before it as e.g. envx$env_of_envs
        # should env_of_envs be defined inside environment envx)
        #
        # NOTE: The call to as.character() below removes any factor levels that may be present in the envmap columns.
        # I define the envmap data frame with the stringsAsFactors=FALSE option but the user may provide
        # an envmap data frame that was not created with this option...
        env_full_names = c(env_full_names, 
                           ifelse(envmap[ind,"path"] != "",
                                  as.character(envmap[ind,"path"]),
                                  as.character(envmap[ind,"location"]))
                           )

        # TODO: (2017/09/26) Implement the pathaddress column in the get_env_names() lookup table!
        # This column should contain the memory address of the environment referred to in the 'path' column
        # which is where the environment being searched for is located when both 'location' and 'path' are non-empty.
        env_addresses = c(env_addresses,
                          ifelse(envmap[ind,"path"] != "",
                                 as.character(envmap[ind,"pathaddress"]),
                                 as.character(envmap[ind,"locationaddress"]))
                              )
      }

      return(list(env_full_names=env_full_names, env_addresses=env_addresses))
  }

  # look_for()
	# Function that searches for an object among the environments included in the 'envmap' lookup table.
  # The name of the object to search for is computed by calling get_obj_name() on 'obj' and requesting
  # to evaluate 'obj' n levels up from the calling environment.
  # Note that 'obj' can ALSO be an environment in which case it is searched for simply in the envmap lookup table.
  #
  # The 'envir' variable defines an optional environment where the environments listed in the 'envmap'
  # lookup table should be evaluated in order to get to the actual environment variable (e.g. in order to
  # get from the string "env1$env2$env" to the actual environment variable referenced by this string
  # using eval(parse(text=env_full_name), envir=envir))
	# When 'envir' is given, the envmap lookup table is assumed to have been created in the 'envir' environment.
  #
  # On the contrary, if 'envir' is NULL, the environment is evaluated using eval() with its default behaviour,
  # i.e. the environment name is first evaluated in the parent environment (parent frame) and then searched
  # for in all parent environments until it is found.
	# When envir=NULL it is assumed that the 'envmap' lookup table was created on the whole workspace.
  #
  # IMPORTANT: Whatever the 'envir' value, every environment in the 'envmap' lookup table is expected to be
  # found when running eval() (i.e. eval() should never give an error that the analyzed environment is not found)
  # It's either a user-defined environment, a function environment, or a named environment (system, package, namespace).
  #
	# Parameters env_full_names and env_addresses are both input and output parameters that are updated with the
  # respective information about the new environments found with this search.
	look_for = function(obj, envmap, env_full_names, env_addresses, n, envir=NULL, include_functions=FALSE, silent=TRUE) {
	  # First check if 'obj' is an environment because we should apply a special (uncommon) process for this case
	  # This is important for any NON-user defined environments (i.e. system, package, namespace, and function execution environments)
	  # for which we CANNOT check for the existence of the object through the exists(obj_name, ...) call done at the very end, because:
	  # - either they are NOT inside other environments (this is the case of system, package, namespace environments)
	  # - or they don't have a name (this is the case for function execution environments)
	  # Note that for user-defined environments, we MUST follow the normal procedure defined in the ELSE block,
	  # because user-defined environments may exist in many different environments and all occurrences should be returned.
	  # (Ref: test T13 in test-environment_name.r)

	  # Now check whether obj is an environment OR get(obj) is a NAMED environment
	  # (the first case happens when e.g. obj = globalenv() and the second case happens when e.g. obj = ".GlobalEnv"
	  # and each of these situations depend on how the user called obj_find().
	  # Note that in the case of get(obj) being an environment, the environment MUST be a named environment;
	  # in fact, if it is not, it is a USER-DEFINED environment  (e.g. obj = "env1")
	  # and in that case we should look for the user-defined environment in the traditional way, because
	  # user-defined environments may exist in different environments (which is NOT the case for
	  # named environments such as system/package/namespace environments, which only exist once!
	  # Note also that the only possibility for get(obj) not being a named environment is that they
	  # are a user-defined environment, i.e. they CANNOT be a function execution environment
	  # because such environments do NOT have a name and therefore they will never pass the
	  # condition is.environment(get(obj))
	  is_environment_obj = is.environment(obj)
	  is_objAsName_a_named_environment = try( {
  	    is_objAsName_an_environment = is.environment(get(obj))
  	    is_named_environment = environmentName(get(obj)) != ""
  	    is_objAsName_an_environment && is_named_environment
	    }, silent=TRUE)
	  if (is_environment_obj ||
	      (!inherits(is_objAsName_a_named_environment, "try-error") && is.logical(is_objAsName_a_named_environment) && is_objAsName_a_named_environment)) {
	    # Create the object containing the environment that should be looked for
	    # which depends on whether obj is the environment (e.g. when obj = globalenv())
	    # or get(obj) is the environment (e.g. when obj = ".GlobalEnv")
	    if (is_environment_obj) {
	      obj_as_environment = obj
	    } else {
	      obj_as_environment = get(obj)
	    }
	    # Note that the following call should update env_full_names and env_addresses with the information
	    # on ALL the environments where 'obj' is found (this information is then stored in the returned value)
	    env_found_list = look_for_environment(obj_as_environment, envmap, env_full_names, env_addresses)
	  } else {
	    # Get the name of 'obj'
	    # This name is retrieved in the calling environment of the function calling this function + n levels up
	    # (where n is the parameter passed to obj_find())
	    # (this is the same as running get_obj_name(obj, n=n+1) in the calling function environment, i.e.
	    # in the main body of obj_find(), where in fact we have already run that, but here we are constructing
	    # obj_name again as a *local* variable --and this is crucial because there are two places in the main
	    # body of obj_find() where this function can be called: in one place it's called with 'obj' as parameter
	    # and in the other place it's called with 'obj_eval' as parameter => obj_name needs to be computed again!)
	    obj_name = get_obj_name(obj, n=n+2, silent=TRUE)

	    # The object is NOT an unnamed environment
	    # => Look for 'obj' using the normal procedure that crawls the environments defined inside the 'envir'
	    # environment whose memory addresses are stored in the 'envmap' table and then call the exists() function
	    # on each of these environments to see if the object exists there.
	    i = 0
	    nenvs = nrow(envmap)
	    # Get the name of the envir environment if any
	    # (this is used to give the environment where the object is found a context (as in e.g. env_of_envs$env1
	    # instead of simply showing "env1" when envir=env_of_envs, so that we make explicit that the environment
	    # where the object is found (env1) is inside env_of_envs.
	    envir_name = NULL
	    if (!is.null(envir)) {
	      envir_name = environment_name(envir)[[1]]
	      ## With [[1]] we get the UNNAMED version of the first element of the returned array.
	      ## This is necessary because environment_name() may return more than one element if the memory address
	      ## of the environment has different names (because e.g. several variables share the same memory address)
	      ## There is NO error when the returned array is NULL.
	    }
	    for (address in envmap[,"address"]) {
	      i = i + 1
	      env_type = as.character(envmap[i,"type"])
	      env_full_name = as.character(envmap[i,"pathname"])
	      env_full_name_with_context = collapse_root_and_member(envir_name, env_full_name)

	      # ONLY check if the object exists in the env_full_name environment if the environment
	      # has not already been checked (repetition happens e.g. for package and namespace environments
	      # which have the same name (e.g. both the base package and its namespace environment
	      # are called "package:base")
	      if (!env_full_name_with_context %in% env_full_names) {
	        if (!silent)
	          cat(i, "of", nenvs, ": Inspecting environment", env_full_name_with_context, "...\n")

	        # Get the environment from the currently analyzed envmap entry
	        # Need to check if the current entry corresponds to an unnamed environment (user-defined) or
	        # to a named environment (system, package, namespace)
	        if (env_type == "user") {
	          # Case for user-defined environments (e.g. those created with new.env())
	          if (is.null(envir)) {
	            env = try( eval(parse(text=env_full_name)), silent=TRUE )
	          } else {
	            env = try( eval(parse(text=env_full_name), envir=envir), silent=TRUE )
	          }
	        } else if (env_type == "function") {
	          # Case for function execution environments
	          if (include_functions) {
	            # Set the environment to search for 'obj_name' as the function's execution environment
	            # (i.e. this allows to search objects defined in fuction execution environments!)
	            env = get_fun_env(address)
	          } else {
	            env = NULL
	          }
	        } else if (env_type %in% c("system/package", "namespace")) {
	          # Case for named environments (mostly packages) (e.g. .GlobalEnv, package:stats, etc.)
	          env = as.environment(env_full_name)
	        } else if (env_type == "empty") {
	          # This is the case when the environment is the empty environment
	          # (which cannot contain any objects, so there is no reason to look for the object there!)
	          env = NULL
	        }

	        # Check whether the object exists in the currently analyzed environment
	        # and if so add the analyzed environment to the list of environments where the object is found
	        if (!inherits(env, "try-error") && !is.null(env) &&		## env could be NULL if env_type = "function" and the function's execution environment could not be retrieved
	            !is.null(obj_name) && obj_name != "" &&        	## This is checked to avoid an error in exists() which does not accept a NULL or empty argument
	            exists(obj_name, envir=env, inherits=FALSE)) { 	## inherits=FALSE avoids searching on the enclosing (i.e. parent) environments
	          env_full_names = c(env_full_names, env_full_name_with_context)
	          env_addresses = c(env_addresses, address)
	        }
	      }
	    }

	    # Store the environment names and addresses in a list to return to the outside world
	    env_found_list = list(env_full_names=env_full_names, env_addresses=env_addresses)
	  }

		return(env_found_list)
	} # look_for()

	# Store the original environment passed in 'envir' in case we need to call obj_find() recursively
	# (in order to search for the *evaluated* 'obj' (obj_eval) as done at the very end of this process),
	# and set envir to the parent environment when it is NULL so that searches for the object take place
	# in the calling environment n+1 levels up (e.g. if we do 'with(env1, obj_find(x, n=2))') or
	# if we call obj_find() from within a function.
	if (is.null(envir)) {
	  envir_orig = NULL
	  if (globalsearch) {
	    envir = .GlobalEnv
	  } else {
	    envir = parent.frame(n+1)
	  }
	} else {
	  envir_orig = envir
	}

	# Get the name of the envir environment to be used in messages
	# Note that this returns "<environment>" when envir is a system environment (e.g. .GlobalEnv, etc.)
	# => 'envir_name' is only meaningful when envir is a user-defined environment in which case it contains
	# the name of the environment (e.g. "env1")
	envir_name = deparse(substitute(envir))

	# Initialize the output variable containing the list of environments
	# (fully specified, i.e. with their paths as well, as in env1$env)
  env_full_names = NULL
  env_addresses = NULL
  found = FALSE

	# Check that the value of parameter 'envir' is an environment
  error = FALSE
  tryCatch(
    if (class(envir) != "environment") {
			error_NotValidEnvironment(envir_name)
      error = TRUE
    },
    error=function(e) {
						error_NotValidEnvironment(envir_name); assign("error", TRUE, inherits=TRUE)
            ## Note the use of the inherits=TRUE parameter which means: search for the variable to be assigned in parent environments and assign the value to the first one found.
          },
		silent=TRUE
  )
  if (error) return(invisible(NULL))

  # Extract the name of the object in the calling function
	# (i.e. the string of the object passed in obj when obj is NOT a string --e.g. when obj = x => obj_name = "x")
	obj_name = get_obj_name(obj, n=n+1, silent=TRUE)
	# Check if obj_name is not NULL and it's not an empty string, o.w. the exists() function below gives an error
	# (the gsub() function removes blanks in the value of obj_name so that if the user passes "   ",
	# nchar() still returns 0, meaning that the name of the object is an empty string)
	if (!is.null(obj_name) && nchar(gsub(" ", "", obj_name)) > 0) {

		### 1.- First check if the object exists in the root of the 'envir' environment
		# In fact the object may exist in the given environment, without need to further search in
		# environments defined within that environment. Of course we also search those below.
	  # NOTE that we only check this if 'envir' was not originally NULL (envir_orig != NULL)
	  # or, despite envir_orig being NULL, the search is supposed to be done locally (globalsearch = FALSE)
	  # --i.e. in the parent environment, which is the value of 'envir' when globalsearch=FALSE and envir_orig=NULL-- 
	  # If neither of this is the case (i.e. if the search is to be done globally) a get_env_names() call
	  # will be done below that will trigger the process of searching for the object in ALL environments
	  # defined in the workspace (including packages).
	  # But more importantly, this check should NOT be done when envir was originally NULL
	  # because the name of the 'envir' environment here is obtained by calling deparse(substitute(envir))
	  # but if the value of envir was originally NULL this will return "<environment>", which doesn't make sense.
	  # Although we could get the correct name of the environment by calling environment_name() I don't want
	  # to call this function here because this implies running a get_env_names(), which is already going to be
	  # run below.
	  # On the contrary, even if envir was originally NULL, we should still search for the object in the
	  # parent environment when globalsearch=FALSE because this will NOT be done below. In this case, we also
	  # have the problem just mentioned about the output of deparse(substitute()) and in this case the problem
	  # will in fact be solved by calling environment_name(), but this is ok, because as I just said
	  # no call to get_env_names() will be done below when globalsearch=FALSE!
	  #
		# IMPORTANT: in all the substitute() calls below, we should use 'envir' and NOT a variable that is set
	  # to be equal to 'envir'. 
	  # In fact, under those circumstances, e.g. if we define a local variable envir_actual = envir,
	  # the result of deparse(substitute(envir_actual)) will give "<environment>" and not the name
	  # of the environment stored in 'envir' (which is what we really want). This happens because
	  # envir_actual stores a local variable ('envir') containing the environment, while 'envir'
	  # stores the actual environment passed by the user! So, the "substitution" of 'envir_actual'
	  # with deparse(substitute()) contains the value of 'envir_actual' as TEXT, i.e. the value of
	  # 'envir' as text, and this results in "<environment>". Instead when the substitution is applied
	  # to 'envir' it returns e.g. "env1", i.e. the name of the object passed as parameter.
		if ((!is.null(envir_orig) || globalsearch == FALSE) &&
	      exists(obj_name, envir=envir, inherits=FALSE)) { # inherits=FALSE avoids searching on the enclosing (i.e. parent) environments
		  # Check whether we are in a recursive call of obj_find(). This is important because that defines
		  # in which environment is 'envir' evaluated, either in the current execution environment
		  # (when there is no recursion involved) or in the parent environment (when recursion is involved)
		  # We check if we are on a recursive call by checking the calling function name
		  # (which could be "obj_find" or e.g. "R_GlobalEnv$obj_find", and therefore we check for both cases).
		  # We then compute the number of levels to go up to evaluate 'envir' by comparing the value of n
		  # with the value of n at the calling environment (n_parent) and compute the different n - n_parent.
		  # Note that normally 'envir' needs to be evaluated in the parent environment (parent.frame(1)),
		  # and not further back, since we expect to have only one recursive call to obj_find() coming from
		  # the case where we check whether we can find the *evaluated* value of 'obj' as done below,
		  # towards the end.
		  fun_calling = get_fun_calling()
		  if (length( grep("^obj_find$", fun_calling) ) + length( grep("^.*\\$obj_find$", fun_calling) ) > 0) {
		    n_parent = eval.parent(quote(n))
  		    ## Note the need to use quote() because o.w. n is first evaluated in the current environment!
	  	    ## (the other option would be to use parse(text="n")))
	      env_full_name = deparse(substitute(envir, parent.frame(abs(n - n_parent))))
		  }
		  else {
		    env_full_name = deparse(substitute(envir))
		  }

		  # Check if the name of the environment makes sense
		  # We check for both:
		  # - "<environment>" (which is the value returned for user-defined environments)
		  # - "envir_orig" which is the case when obj_find() was called recursively but where the
		  # VERY FIRST call to obj_find() was done by passing the function as an argument of another
		  # function (e.g. sapply() or lapply(), in which case the calling function is "FUN" not "obj_find"
		  # --since the argument of these functions receiving the function name to run is called "FUN")
		  # Note that if the actual environment passed by the user IS actually called "envir_orig", this is no problem
		  # because that value will be resolved next when calling environment_name() (which will return "envir_orig"
		  # the expected value when envir=envir_orig))
		  # Note that the string "envir_orig" should coincide with the variable name used above to store
		  # the original value of 'envir' before updating its value in this function.
		  if (env_full_name == "<environment>" || env_full_name == "envir_orig") {
		    # This means that the 'envir' environment could not be resolved to a name because for example
		    # is equal to the calling environment (which happens when envir=NULL and globalsearch=FALSE)
		    # and the calling environment doesn't have a name, e.g. when obj_find() is called as:
		    # 	with(env1, obj_find(x))
		    # => Get the name of the environment by calling environment_name() with envir set to the parent frame
		    # of the calling environment (this was proved to work on a simulation test)
		    env_full_name = environment_name(envir, envir=parent.env(envir))
		  }
		  # Store the address of the environment whose name is stored in env_full_name so that we can
		  # add the address of other environments where the object is possibly found later on by
		  # looking for the object (recursively in existing environments as done in Step 2 below)
		  # NOTE that:
		  # - only one environment is listed in env_full_name. (because env_full_name contains the name of the environment
		  # referenced by 'envir', which is only ONE)
		  # - this should be done BEFORE standardizing the environment name! (as e.g. "R_GlobalEnv" because the parse() function
		  # gives the error that no object called "R_GlobalEnv" is found)
		  env_address = try( address( eval(parse(text=env_full_name)) ) )
		  if (inherits(env_address, "try-error")) {
		    env_address = NULL
		  }

		  # Standardize the environment name (this is important because a further search for the object
		  # may be carried out below --by calling look_for() when globalsearch=TRUE-- and the environment name should be already
		  # standardized if we don't want the global environment or the base environment to appear twice)
		  env_full_name = standardize_env_name(env_full_name)

		  # Add the information on the environments where the object was found to the arrays holding this information
		  env_full_names = c(env_full_names, env_full_name)
		  env_addresses = c(env_addresses, env_address)
		}

		### 2.- Look for the object inside any environments defined within 'envir' (recursively through the envmap lookup table)
	  # When envir_orig=NULL, how this search is done depends on parameter globalsearch:
	  # - if globalsearch = TRUE  => the search is done on the whole workspace (envir=NULL)
	  # - if globalsearch = FALSE => the search is done solely on the environments found
	  # inside the 'envir' environment (which is the parent environment (a.k.a. calling environment))
		# Recall that if the original parameter envir is NULL, it has already been set to .GlobalEnv,
	  # and this allows to search for the object in the whole workspace if globalsearch=TRUE
	  # (simply by constructing the 'envmap' lookup table on the whole workspace (i.e. using envir=NULL
	  # in the call to get_env_names()).
	  # Still if globalsearch=FALSE and the original value of envir is NULL, envir could have been
	  # set to .GlobalEnv (if the calling environment is the global environment), and in this case the
	  # object is searched for only within the global enviroment (i.e. it is not searched within packages
	  # or namespaces and the environments therein).
	  # 
	  # SO HERE we go over all the environments stored in envmap and check if the object is there
	  # Note that the first parameter in the call to look_for() must be 'obj_name' and NOT 'obj'
	  # because 'obj' will return the 'value* of the variable passed NOT its *name* (which is what we need here)
	  if (is.null(envir_orig) && globalsearch) {
			if (is.null(envmap)) envmap = get_env_names(envir=NULL)
	    env_found = look_for(obj_name, envmap, env_full_names, env_addresses, n, envir=NULL, include_functions=include_functions, silent=silent)
	  } else {
	    if (is.null(envmap)) envmap = get_env_names(envir=envir)
	    env_found = look_for(obj_name, envmap, env_full_names, env_addresses, n, envir=envir, include_functions=include_functions, silent=silent)
	  }
	  # UPDATE the arrays holding the information on the environments where the object was found after
	  # any potentially new environment was found by the look_for() function
	  env_full_names = env_found$env_full_names # This may return a SET of environments (when the object is found in several environments)
	  env_addresses = env_found$env_addresses

		if (is.null(env_full_names)) {
		  # If still the object was not found...
		  ### 3.- Check if 'obj' was given as an expression that includes the environment path to the object
		  ### as in 'env1$x' or 'globalenv()$env1$x'
		  # (Note that this step must come BEFORE checking if the object can be evaluated (step 4 below)
		  # because the expression that would pass the test we do now (e.g. env1$y) could evaluate to the name
		  # of an object (e.g. "x"), but in this case we assume that the user is not interested in finding
		  # the object given by the value of env1$y but rather in finding whether object env1$y exists)

		  # Check if the environment path is actually an environment, if not, do not look for obj_name because
		  # it wouldn't mean anything. In fact for instance...
		  # - if obj = alist$v and alist is not an environment then we wouldn't want to look for object "v"
		  # because 'v' is just part of a list, not of an environment (and perhaps an object called "v" even
		  # exists somewhere and we would be looking for it whereas that has nothing to do with the original request!)
		  # - however, if obj = alist$v and alist$v resolves to a variable name, say "x", then we would like to
		  # look for the object called "x". This is done in step 4 below.
		  obj_with_path = check_object_with_path(obj_name, envir=envir, checkenv=TRUE)
		  if (obj_with_path$ok && obj_with_path$env_found) {
		    # Check if the object can be resolved in the 'envir' environment (where the search for the object
		    # is being carried out) or in any parent environment.
		    # Looking for parent environments is important because the user may have called obj_find as:
		    #   with(env1, obj_find(globalenv()$env2$x))
		    # in which case the object ('globalenv()$env2$x') should be found in the global environment
		    # even if we are calling obj_find() from within the 'env1' environment...
		    # In fact, if we run with(env1, globalenv()$env2$x), we will get the value
		    # of x inside the globalenv()$env2 environment.
		    # TODO: (2016/10/09) Should this search be with inherits=FALSE when globalsearch=FALSE?
		    # (even taking into account what I just wrote about searching for 'globalenv()$env2$x')
				# In that case, inside check_object_exists(), instead of calling eval() on 'obj' we would need
				# to call exists() on the object name (i.e. obj_with_path$name) on the environment path made up of
				# 'envir' -> obj_with_path$env_name using the option inherits=FALSE. Note that this can be done
				# by calling exists() as e.g.:
				#		exists(obj_name, envir=eval(parse(text=env_name)))
				# where e.g. obj_name = "x" and env_name = "env_of_envs$env1" (note that the $ is accepted by eval()!)
		    check_obj_exist = check_object_exists(obj, envir)
		    if (check_obj_exist$found) {
		      # Store the information about the environment extracted from the object name
		      # (i.e. the full environment path as in env_of_envs$env1)
					# TODO: (2016/10/09) When globalsearch=TRUE, try to solve the issue arising when the object exists
		      # in an environment different from 'envir'
					# and still is found (because the eval() call inside check_object_exists() is performed on all
					# parent environments from 'envir') but the name stored in env_full_names in that case is the name
					# of the environment path indicated in 'obj' (e.g. "env_of_envs$env1"). This would suggest that
					# such environment exists in the 'envir' environment but this is not true in this case, as it
					# exists in ANOTHER environment, whose name we are not giving. So the solution would be to add the
					# environment where the object is found to the environment path and assign e.g. the following to
					# env_full_names: "envwhereitwasfound$env_of_envs$env1".
					# Perhaps this could be done by calling get_env_names() on the whole workspace and then calling
					# look_for('obj') on the constructed envmap lookup table and using envir=NULL. 
		      env_full_names = c(env_full_names, obj_with_path$env_name)
		      env_addresses = c(env_addresses, check_obj_exist$address)
		    }
		  }

		  if (is.null(env_full_names)) {
		    # If still the object was not found... give it a last chance!
		    ### 4.- Try to see if obj is an expression whose *evaluation* exists (see more on the next line)
		    # Note that the object is evaluated in the environment n levels up from the current environment
		    # (this is the meaning of 'n', i.e. how many levels up should 'obj' be evaluated)
		    # or in any parent environment until it is found.
		    # Note also that we set the warn option to "no warning" in order to
		    # avoid the warning message "restarting interrupted promise evaluation"
		    # when the obj object does not exist. This happens when the program already
		    # tried to evaluate the object unsuccessfully.
		    # We re-establish the original warn value further down after different try() calls
		    # have been carried out.
		    # See more at: http://stackoverflow.com/questions/20596902/r-avoiding-restarting-interrupted-promise-evaluation-warning
		    set_option_warn_to_nowarning()
		    obj_eval <- try(eval(obj, envir=parent.frame(n+1)), silent=TRUE)

		    #--- Check first if obj_eval yields an error or resolves to a value but obj is still a valid object to search for 
		    # This check is two-fold:
		    # - on one side we check whether the evaluated object obj_eval and the original object obj are different
		    # - on the other side we check whether the try( eval() ) call above yielded an error.
		    # If any of these is the case, obj_eval is replaced with obj, because obj may contain the object
		    # we are looking for.
		    # This happens for instance when:
		    # - obj = as.name(y) where y = "x", i.e. a string we are treating as a symbol (because of as.name())
		    # - obj = v[[1]] where v is a list of symbols, as in v = c(quote(x), quote(y)) (note that is a 
		    # list in this case NOT an array! --R behaviour, presumably because each element of the "array" is
		    # storing a complicated object)
		    # In these examples, x is the object we are looking for and two situations may happen,
		    # which derive respectively into the two checks mentioned above:
		    # - either x exist in the calling environment n levels up (therefore obj_eval contains
		    # the evaluation of x --e.g. 3)
		    #   --> in this case obj_eval != obj so we should assign obj to obj_eval so that we can
		    #       look for object x (not for the value 3 as an object!)
		    # - or x does not exist in the calling environment n levels up 
		    #   --> in this case the try(eval()) yields an error so we should assign obj to obj_eval
		    #       so that we can look for object x.
		    # Note that this does not happen if obj = v[1] in the above example (i.e. only one pair of brackets,
		    # not two as in [[1]]), because in that case v[1] is a list and the above try(eval()) does not give an error.
		    is_obj_eval_different_from_obj <- try(obj_eval != obj, silent=TRUE)
		    if (inherits(obj_eval, "try-error") ||
		        inherits(is_obj_eval_different_from_obj, "try-error") ||
		          ## If there was an error in 'obj_eval != obj' then it means that the result of the comparison is TRUE
		          ## (this happens for instance when obj_eval is an environment and obj is a string, and this type of
		          ## comparisons cannot be performed)
		        !inherits(is_obj_eval_different_from_obj, "try-error") && 
		        is_logical(is_obj_eval_different_from_obj) &&
		            ## Before checking the truth value of the result of the above comparison
		            ## stored in is_obj_eval_different_from_obj, we need to check whether the
		            ## value stored is a valid logical value
		            ## (e.g. has length > 0 or is not NA) because if obj_eval or obj are NULL or NA
		            ## the comparison will yield logical(0) or NA and an error will be raised
		            ## when checking for the truth value of is_obj_eval_different_from_obj.
		        is_obj_eval_different_from_obj) {
		      # => Assign the searched object to obj_eval
		      # This is the case when e.g. obj = v[[1]], i.e. an element of a list
		      # containing a name (e.g. 'x') that does not exist in the calling environment 
		      # n levels up (meaning that the try(eval(obj)) above returns an error)
		      # But if this is the case, we still want to search for symbol 'x'.
		      obj_eval <- try(obj, silent=TRUE)
		    }
		    reset_option_warn()

		    #--- Now process obj_eval
		    if (!inherits(obj_eval, "try-error")) {
		      if (is.list(obj_eval) && length(obj_eval) == 1) {
		        # Extract the single element of the list as obj_eval (this is the case for example when
		        # obj = v[1] and v is a vector that contains e.g. environments or functions... in that case
		        # the evaluation of v[1] returns a list with a single element whose value is the environment
		        # or function contained in v[1])
		        obj_eval = obj_eval[[1]]
		      }
		      #--------------- 4a. Try looking for the evaluated object with look_for() ------------------
		      if (is.null(envir_orig) && globalsearch) {
		        env_found = look_for(obj_eval, envmap, env_full_names, env_addresses, n, envir=NULL, include_functions=include_functions, silent=silent)
		      } else {
		        env_found = look_for(obj_eval, envmap, env_full_names, env_addresses, n, envir=envir, include_functions=include_functions, silent=silent)
		      }
		      # Update the information about the environments where the object was found so far
		      env_full_names = env_found$env_full_names # This may contain a SET of environments (when the object is found in several environments)
		      env_addresses = env_found$env_addresses

		      if (is.null(env_full_names)) {
		        #----- 4b. Recurse and check if we can find the EVALUATED object following the same process carried out above ------
		        # Compare the object evaluation with the object name in order to avoid entering an infinite loop
		        # (i.e. here we rule out the case when e.g. obj_name = "3" and obj_eval = 3 in which case we should
		        # stop looking for the object '3'!)
		        # Note that we enclose the comparison of obj_eval with obj_name in a try block because obj_eval
		        # can be any kind of object and that object may not accept a comparison with a string (for instance
		        # if the obj_eval is a function like e.g. 'mean')
		        is_obj_eval_different_from_obj_name = try( obj_eval != obj_name, silent=TRUE )
		        if (is_logical(is_obj_eval_different_from_obj_name) &&
  		            ## Need to first check if the result of the above comparison is a valid logical value
		              ## (e.g. has length > 0 or is not NA) because if obj_eval or obj are NULL or NA
  		            ## the comparison will yield logical(0) or NA and an error will be raised.
		            ( !is.null(obj_eval) &&
		              (inherits(is_obj_eval_different_from_obj_name, "try-error") || # If there was an error in 'obj_eval != obj_name' then it means that the result of the comparison is TRUE => we can recurse to look for obj_eval)
		               !inherits(is_obj_eval_different_from_obj_name, "try-error") && is_obj_eval_different_from_obj_name)) ) {
		          # Repeat the obj_find() process on the EVALUATED object
		          # This is important when e.g. alist$v = "env1$x" and object 'x' exists in environment "env1"
		          # Note that we use envir=envir_orig, in order to repeat exactly the original call to obj_find()
		          # where any envir=NULL value has not been replaced with envir=.GlobalEnv.
		          obj_found = obj_find(obj_eval, envir=envir_orig, globalsearch=globalsearch, n=n+1, return_address=return_address, include_functions=include_functions, silent=silent)

		          # Update the output variables with the environment information where the object was found
		          # NOTE that at this point these variables (env_full_names and env_addresses) should have 0 length
		          # because if we reached this point it's because we hadn't found the object in any environment
		          # prior to entering this Step 4b!
		          if (return_address) {
		            env_full_names = obj_found$env_full_names
		            env_addresses = obj_found$env_addresses
		          } else {
		            env_full_names = obj_found
		          }
		        }
		      } else {
		        # Assign the addresses to each environment in env_full_names
		        # NOTE that:
		        # - at this stage there may be several environments stored in env_full_names and therefore we need to call the sapply() function
		        # - we need to call the destandardize() function because environments stored as e.g. R_GlobalEnv should be converted to their
		        # "original" names, i.e. the names that are understood by R (e.g. .GlobalEnv)), o.w. the parse() function gives the error that
		        # object e.g. "R_GlobalEnv" is not found.
		        # - the eval(parse()) expression used below simply requests the address of the environment whose name is the one given in text=.
		        # Note that we DO NOT need to enclose this evaluation in a try() block because the environment names stored in
		        # env_full_names correspond to valid environments as the object was found there!
		        #env_addresses = sapply(env_full_names, function(x) { address(eval(parse(text=destandardize_env_name(x)))) })
		      }
		    }
		  }
		}

		if (!is.null(env_full_names)) {
			# Standardize the names of the environment so that the global and the base environments are always shown
			# the same way, regardless of how the 'envir' parameter is passed.
			env_full_names = sapply(env_full_names, FUN=standardize_env_name, USE.NAMES=FALSE)
			found = TRUE
		}
	}

	# Sort environments by name (so that the user has a consistent way of seeing the list of environments where the object was found)
	if (!is.null(env_full_names)) {
	  ord = order(env_full_names)
	  env_full_names = env_full_names[ord]
	  env_addresses = env_addresses[ord]
	}

  if (!silent) {
    if (found) {
      cat("Object", obj_name, "found in the following environments:\n")
      print(env_full_names)
    } else {
      cat("The object was not found in any environment\n");
    }
    if (return_address) {
      return(invisible(list(env_full_names=env_full_names, env_addresses=env_addresses)))  # Return invisible() because we already printed the environments where the object was found
    } else {
      return(invisible(env_full_names))  # Return invisible() because we already printed the environments where the object was found
    }
  } else {
    if (return_address) {
      return(list(env_full_names=env_full_names, env_addresses=env_addresses))             # Return non-invisible, because we want to show the environments where the object was found
    } else {
      return(env_full_names)             # Return non-invisible, because we want to show the environments where the object was found
    }
  }
}

Try the envnames package in your browser

Any scripts or data that you put into this service are public.

envnames documentation built on Dec. 8, 2020, 9:07 a.m.