## NOTE ON S3 METHODS: New S3 methods are not automatically registered. You can
|
|
## register them manually after you have inserted method_name.my_class into your
|
|
## package environment using ess-developer, like follows:
|
|
##
|
|
## registerS3method("method_name", "my_class", my_package:::method_name.my_class)
|
|
##
|
|
## If an S3 methods already exists in a package, ESS-developer will do the right
|
|
## thing.
|
|
|
|
## evaluate the STRING by saving into a file and calling .ess.ns_source
|
|
.ess.ns_eval <- function(string, visibly, output, package,
|
|
file = tempfile("ESSDev"), verbose = FALSE,
|
|
fallback_env = NULL, local_env = parent.frame()) {
|
|
cat(string, file = file)
|
|
on.exit(.ess.file.remove(file))
|
|
.ess.ns_source(file, visibly, output, package = package,
|
|
verbose = verbose, fake.source = TRUE,
|
|
fallback_env = fallback_env, local_env = local_env)
|
|
}
|
|
|
|
|
|
##' Source FILE into an environment. After having a look at each new object in
|
|
##' the environment, decide what to do with it. Handles plain objects,
|
|
##' functions, existing S3 methods, S4 classes and methods.
|
|
##' @param fallback_env environment to assign objects which don't exist in the
|
|
##' package namespace
|
|
.ess.ns_source <- function(file, visibly, output, expr,
|
|
package = "", verbose = FALSE,
|
|
fake.source = FALSE,
|
|
fallback_env = NULL,
|
|
local_env = NULL) {
|
|
oldopts <- options(warn = 2)
|
|
on.exit(options(oldopts))
|
|
pname <- paste("package:", package, sep = "")
|
|
envpkg <- tryCatch(as.environment(pname), error = function(cond) NULL)
|
|
if (is.null(envpkg))
|
|
if (require(package, quietly = TRUE, character.only = TRUE)) {
|
|
envpkg <- tryCatch(as.environment(pname), error = function(cond) NULL)
|
|
} else {
|
|
## no such package; source in current (local) user environment
|
|
return(.ess.source(file, visibly = visibly,
|
|
output = output, local = local_env,
|
|
fake.source = fake.source))
|
|
}
|
|
|
|
envns <- tryCatch(asNamespace(package), error = function(cond) NULL)
|
|
if (is.null(envns))
|
|
stop(gettextf("Can't find a namespace environment corresponding to package name '%s\"",
|
|
package), domain = NA)
|
|
|
|
## Here we know that both envns and envpkg exists and are environments
|
|
if (is.null(fallback_env))
|
|
fallback_env <- .ess.ns_insert_essenv(envns)
|
|
|
|
## Get all Imports envs where we propagate objects
|
|
pkgEnvNames <- Filter(.ess.is_package, search())
|
|
packages <- lapply(pkgEnvNames, function(envName) substring(envName, 9))
|
|
importsEnvs <- lapply(packages, function(pkgName) parent.env(asNamespace(pkgName)))
|
|
|
|
## Evaluate the FILE into new ENV
|
|
env <- .ess.ns_evalSource(file, visibly, output, substitute(expr), package, fake.source)
|
|
envPackage <- getPackageName(env, FALSE)
|
|
if (nzchar(envPackage) && envPackage != package)
|
|
warning(gettextf("Supplied package, %s, differs from package inferred from source, %s",
|
|
sQuote(package), sQuote(envPackage)), domain = NA)
|
|
|
|
## Get all sourced objects, methods and classes
|
|
allObjects <- objects(envir = env, all.names = TRUE)
|
|
allObjects <- allObjects[!(allObjects %in% c(".cacheOnAssign", ".packageName"))]
|
|
MetaPattern <- methods:::.TableMetaPattern()
|
|
ClassPattern <- methods:::.ClassMetaPattern()
|
|
allPlainObjects <- allObjects[!(grepl(MetaPattern, allObjects) |
|
|
grepl(ClassPattern, allObjects))]
|
|
allMethodTables <- allObjects[grepl(MetaPattern, allObjects)]
|
|
allClassDefs <- allObjects[grepl(ClassPattern, allObjects)]
|
|
|
|
## PLAIN OBJECTS and FUNCTIONS:
|
|
funcNs <- funcPkg <- newFunc <- newNs <- newObjects <- newPkg <- objectsNs <- objectsPkg <- character()
|
|
dependentPkgs <- list()
|
|
|
|
for (this in allPlainObjects) {
|
|
thisEnv <- get(this, envir = env)
|
|
thisNs <- NULL
|
|
|
|
## NS
|
|
if (exists(this, envir = envns, inherits = FALSE)){
|
|
thisNs <- get(this, envir = envns)
|
|
if(is.function(thisNs) || is.function(thisEnv)){
|
|
if(is.function(thisNs) && is.function(thisEnv)){
|
|
if(.ess.differs(thisEnv, thisNs)){
|
|
environment(thisEnv) <- environment(thisNs)
|
|
.ess.assign(this, thisEnv, envns)
|
|
funcNs <- c(funcNs, this)
|
|
if(exists(".__S3MethodsTable__.", envir = envns, inherits = FALSE)){
|
|
S3_table <- get(".__S3MethodsTable__.", envir = envns)
|
|
if(exists(this, envir = S3_table, inherits = FALSE))
|
|
.ess.assign(this, thisEnv, S3_table)
|
|
}
|
|
}
|
|
}else{
|
|
newNs <- c(newNs, this)
|
|
}
|
|
}else{
|
|
if(!identical(thisEnv, thisNs)){
|
|
.ess.assign(this, thisEnv, envns)
|
|
objectsNs <- c(objectsNs, this)
|
|
}
|
|
}
|
|
}else{
|
|
newNs <- c(newNs, this)
|
|
}
|
|
|
|
## PKG
|
|
if (exists(this, envir = envpkg, inherits = FALSE)){
|
|
thisPkg <- get(this, envir = envpkg)
|
|
if(is.function(thisPkg) || is.function(thisEnv)){
|
|
if(is.function(thisPkg) && is.function(thisEnv)){
|
|
if(.ess.differs(thisPkg, thisEnv)){
|
|
environment(thisEnv) <- environment(thisPkg)
|
|
.ess.assign(this, thisEnv, envpkg)
|
|
funcPkg <- c(funcPkg, this)
|
|
}
|
|
}else{
|
|
newPkg <- c(newPkg, this)
|
|
}
|
|
}else{
|
|
if(!identical(thisPkg, thisEnv)){
|
|
.ess.assign(this, thisEnv, envpkg)
|
|
objectsPkg <- c(objectsPkg, this)
|
|
}
|
|
}
|
|
}else{
|
|
newPkg <- c(newPkg, this)
|
|
}
|
|
|
|
if (!is.null(thisNs)) {
|
|
isDependent <- .ess.ns_propagate(thisEnv, this, importsEnvs)
|
|
newDeps <- stats::setNames(list(packages[isDependent]), this)
|
|
dependentPkgs <- c(dependentPkgs, newDeps)
|
|
}
|
|
}
|
|
|
|
## deal with new plain objects and functions
|
|
for (this in intersect(newPkg, newNs)) {
|
|
thisEnv <- get(this, envir = env, inherits = FALSE)
|
|
if (exists(this, envir = fallback_env, inherits = FALSE)){
|
|
thisGl <- get(this, envir = fallback_env)
|
|
if (.ess.differs(thisEnv, thisGl)) {
|
|
if (is.function(thisEnv)) {
|
|
environment(thisEnv) <- envns
|
|
newFunc <- c(newFunc, this)
|
|
} else {
|
|
newObjects <- c(newObjects, this)
|
|
}
|
|
.ess.assign(this, thisEnv, fallback_env)
|
|
if (.is.essenv(fallback_env))
|
|
.ess.assign(this, thisEnv, .GlobalEnv)
|
|
}
|
|
} else {
|
|
if (is.function(thisEnv)) {
|
|
environment(thisEnv) <- envns
|
|
newFunc <- c(newFunc, this)
|
|
} else {
|
|
newObjects <- c(newObjects, this)
|
|
}
|
|
.ess.assign(this, thisEnv, fallback_env)
|
|
if (.is.essenv(fallback_env))
|
|
.ess.assign(this, thisEnv, .GlobalEnv)
|
|
}
|
|
}
|
|
|
|
if(length(funcNs))
|
|
objectsNs <- c(objectsNs, sprintf("FUN[%s]", paste(funcNs, collapse = ", ")))
|
|
if(length(funcPkg))
|
|
objectsPkg <- c(objectsPkg, sprintf("FUN[%s]", paste(funcPkg, collapse = ", ")))
|
|
if(length(newFunc))
|
|
newObjects <- c(newObjects, sprintf("FUN[%s]", paste(newFunc, collapse = ", ")))
|
|
|
|
## CLASSES
|
|
classesPkg <- classesNs <- newClasses <- character()
|
|
for(this in allClassDefs){
|
|
newPkg <- newNs <- FALSE
|
|
thisEnv <- get(this, envir = env)
|
|
if(exists(this, envir = envpkg, inherits = FALSE)){
|
|
if(!.ess.identicalClass(thisEnv, get(this, envir = envpkg))){
|
|
.ess.assign(this, thisEnv, envir = envpkg)
|
|
classesPkg <- c(classesPkg, this)
|
|
}
|
|
}else{
|
|
newPkg <- TRUE
|
|
}
|
|
if(exists(this, envir = envns, inherits = FALSE)){
|
|
if(!.ess.identicalClass(thisEnv, get(this, envir = envns))){
|
|
.ess.assign(this, thisEnv, envir = envns)
|
|
classesNs <- c(classesNs, this)
|
|
}
|
|
}else{
|
|
newNs <- TRUE
|
|
}
|
|
if(newNs && newPkg){
|
|
if(exists(this, envir = fallback_env, inherits = FALSE)){
|
|
if(!.ess.identicalClass(thisEnv, get(this, envir = fallback_env))){
|
|
.ess.assign(this, thisEnv, envir = fallback_env)
|
|
newClasses <- c(newClasses, this)
|
|
}
|
|
}else{
|
|
.ess.assign(this, thisEnv, envir = fallback_env)
|
|
newClasses <- c(newClasses, this)
|
|
}
|
|
}
|
|
}
|
|
if(length(classesPkg))
|
|
objectsPkg <- gettextf("CLS[%s]", sub(ClassPattern, "", paste(classesPkg, collapse = ", ")))
|
|
if(length(classesNs))
|
|
objectsNs <- gettextf("CLS[%s]", sub(ClassPattern, "", paste(classesNs, collapse = ", ")))
|
|
if(length(newClasses))
|
|
newObjects <- gettextf("CLS[%s]", sub(ClassPattern, "", paste(newClasses, collapse = ", ")))
|
|
|
|
## METHODS:
|
|
## Method internals: For efficiency reasons setMethod() caches
|
|
## method definition into a global table which you can get with
|
|
## 'getMethodsForDispatch' function, and when a method is dispatched that
|
|
## table is used. When ess-developer is used to source method definitions the
|
|
## two copies of the functions are identical up to the environment. The
|
|
## environment of the cached object has namespace:foo as it's parent but the
|
|
## environment of the object in local table is precisely namspace:foo. This
|
|
## does not cause any difference in evaluation.
|
|
methodNames <- allMethodTables
|
|
methods <- sub(methods:::.TableMetaPrefix(), "", methodNames)
|
|
methods <- sub(":.*", "", methods)
|
|
methodsNs <- newMethods <- character()
|
|
for (i in seq_along(methods)){
|
|
table <- methodNames[[i]]
|
|
tableEnv <- get(table, envir = env)
|
|
if(exists(table, envir = envns, inherits = FALSE)){
|
|
inserted <- .ess.ns_insertMethods(tableEnv, get(table, envir = envns), envns)
|
|
if(length(inserted))
|
|
methodsNs <- c(methodsNs, gettextf("%s{%s}", methods[[i]], paste(inserted, collapse = ", ")))
|
|
}else if(exists(table, envir = fallback_env, inherits = FALSE)){
|
|
inserted <- .ess.ns_insertMethods(tableEnv, get(table, envir = fallback_env), envns)
|
|
if(length(inserted))
|
|
newMethods <- c(newMethods, gettextf("%s{%s}", methods[[i]], paste(inserted, collapse = ", ")))
|
|
}else{
|
|
.ess.assign(table, tableEnv, envir = fallback_env)
|
|
newMethods <- c(newMethods, gettextf("%s{%s}", methods[[i]], paste(objects(envir = tableEnv, all.names = T), collapse = ", ")))
|
|
}
|
|
}
|
|
if(length(methodsNs))
|
|
objectsNs <- c(objectsNs, gettextf("METH[%s]", paste(methodsNs, collapse = ", ")))
|
|
if(length(newMethods))
|
|
newObjects <- c(newObjects, gettextf("METH[%s]", paste(newMethods, collapse = ", ")))
|
|
|
|
if (verbose) {
|
|
msgs <- unlist(list(
|
|
if(length(objectsPkg))
|
|
sprintf("PKG: %s", paste(objectsPkg, collapse = ", ")),
|
|
if(length(objectsNs))
|
|
sprintf("NS: %s", paste(objectsNs, collapse = ", ")),
|
|
if(length(dependentPkgs))
|
|
.ess.ns_format_deps(dependentPkgs),
|
|
if(length(newObjects)) {
|
|
env_name <- .ess.ns_env_name(fallback_env)
|
|
sprintf("%s: %s", env_name, paste(newObjects, collapse = ", "))
|
|
}))
|
|
if(length(msgs))
|
|
.ess_mpi_message(paste(msgs, collapse = " "))
|
|
|
|
}
|
|
|
|
invisible(env)
|
|
}
|
|
|
|
|
|
.ess.ns_insertMethods <- function(tableEnv, tablePkg, envns) {
|
|
inserted <- character()
|
|
for(m in ls(envir = tableEnv, all.names = T)){
|
|
if(exists(m, envir = tablePkg, inherits = FALSE)){
|
|
thisEnv <- get(m, envir = tableEnv)
|
|
thisPkg <- get(m, envir = tablePkg)
|
|
if(is(thisEnv, "MethodDefinition") && is(thisPkg, "MethodDefinition") &&
|
|
.ess.differs(thisEnv@.Data, thisPkg@.Data)){
|
|
environment(thisEnv@.Data) <- envns
|
|
## environment of cached method in getMethodsForDispatch table is still env
|
|
## not a problem as such, but might confuse users
|
|
.ess.assign(m, thisEnv, tablePkg)
|
|
inserted <- c(inserted, m)
|
|
}}}
|
|
inserted
|
|
}
|
|
|
|
## our version of R's evalSource
|
|
.ess.ns_evalSource <- function(file, visibly, output, expr, package = "",
|
|
fake.source = FALSE) {
|
|
envns <- tryCatch(asNamespace(package), error = function(cond) NULL)
|
|
if(is.null(envns))
|
|
stop(gettextf("Package \"%s\" is not attached and no namespace found for it",
|
|
package), domain = NA)
|
|
env <- new.env(parent = envns)
|
|
env[[".packageName"]] <- package
|
|
methods:::setCacheOnAssign(env, TRUE)
|
|
if (missing(file))
|
|
eval(expr, envir = env)
|
|
else if (is(file, "character"))
|
|
for (f in file) {
|
|
.ess.source(f, local = env, visibly = visibly,
|
|
output = output, keep.source = TRUE,
|
|
max.deparse.length = 300,
|
|
fake.source = fake.source,
|
|
message.prefix = sprintf("[%s] ", package))
|
|
}
|
|
else stop(gettextf("Invalid file argument: got an object of class \"%s\"",
|
|
class(file)[[1]]), domain = NA)
|
|
env
|
|
}
|
|
|
|
|
|
.ess.assign <- function(x, value, envir) {
|
|
## Cannot add bindings to locked environments
|
|
exists <- exists(x, envir = envir, inherits = FALSE)
|
|
if (exists && bindingIsLocked(x, envir)) {
|
|
unlockBinding(x, envir)
|
|
assign(x, value, envir = envir, inherits = FALSE)
|
|
op <- options(warn = -1)
|
|
on.exit(options(op))
|
|
lockBinding(x, envir)
|
|
} else if (exists || !environmentIsLocked(envir)) {
|
|
assign(x, value, envir = envir, inherits = FALSE)
|
|
} else {
|
|
warning(sprintf("Cannot assign `%s` in locked environment", x),
|
|
call. = FALSE)
|
|
}
|
|
invisible(NULL)
|
|
}
|
|
|
|
.ess.identicalClass <- function(cls1, cls2, printInfo = FALSE) {
|
|
slots1 <- slotNames(class(cls1))
|
|
slots2 <- slotNames(class(cls2))
|
|
if(identical(slots1, slots2)){
|
|
vK <- grep("versionKey", slots1)
|
|
if(length(vK))
|
|
slots1 <- slots2 <- slots1[-vK]
|
|
out <- sapply(slots1, function(nm) identical(slot(cls1, nm), slot(cls2, nm)))
|
|
if(printInfo) print(out)
|
|
all(out)
|
|
}
|
|
}
|
|
|
|
.ess.differs <- function(f1, f2) {
|
|
if (is.function(f1) && is.function(f2)){
|
|
!(identical(body(f1), body(f2)) && identical(args(f1), args(f2)))
|
|
}else
|
|
!identical(f1, f2)
|
|
}
|
|
|
|
|
|
.ess.is_package <- function(envName) {
|
|
isPkg <- identical(substring(envName, 0, 8), "package:")
|
|
isPkg && (envName != "package:base")
|
|
}
|
|
|
|
.ess.ns_propagate <- function(obj, name, importsEnvs) {
|
|
containsObj <- vapply(importsEnvs, logical(1), FUN = function(envs) {
|
|
name %in% names(envs)
|
|
})
|
|
|
|
lapply(importsEnvs[containsObj], .ess.assign,
|
|
x = name, value = obj)
|
|
|
|
containsObj
|
|
}
|
|
|
|
|
|
.ess.ns_format_deps <- function(dependentPkgs) {
|
|
pkgs <- unique(unlist(dependentPkgs, use.names = FALSE))
|
|
|
|
lapply(pkgs, function(pkg) {
|
|
isDep <- vapply(dependentPkgs, function(deps) pkg %in% deps, logical(1))
|
|
pkgDependentObjs <- names(dependentPkgs[isDep])
|
|
sprintf("DEP:%s [%s] ", pkg, paste(pkgDependentObjs, collapse = ", "))
|
|
})
|
|
}
|
|
|
|
.ess.ns_env_name <- function(env) {
|
|
name <- environmentName(env)
|
|
name <-
|
|
if (name == "") "Local"
|
|
else if (grepl("^essenv:", name)) "NEW"
|
|
else name
|
|
name
|
|
}
|
|
|
|
|
|
.ess.ns_insert_essenv <- function(nsenv) {
|
|
if (is.character(nsenv))
|
|
nsenv <- base::asNamespace(nsenv)
|
|
stopifnot(isNamespace(nsenv))
|
|
if (identical(nsenv, .BaseNamespaceEnv))
|
|
return(.GlobalEnv)
|
|
essenv_name <- sprintf("essenv:%s", environmentName(nsenv))
|
|
nsenv_parent <- parent.env(nsenv)
|
|
if (environmentName(nsenv_parent) == essenv_name) {
|
|
return(nsenv_parent)
|
|
}
|
|
essenv <- new.env(parent = nsenv_parent)
|
|
essenv[[".__ESSENV__."]] <- TRUE
|
|
attr(essenv, "name") <- essenv_name
|
|
nssym <- ".__NAMESPACE__."
|
|
nssym_val <- get(nssym, envir = nsenv, inherits = FALSE)
|
|
unlockBinding(nssym, nsenv)
|
|
nsenv[[nssym]] <- NULL
|
|
on.exit({
|
|
nsenv[[nssym]] <- nssym_val
|
|
lockBinding(nssym, nsenv)
|
|
})
|
|
parent.env(nsenv) <- essenv
|
|
essenv
|
|
}
|
|
|
|
.is.essenv <- function(env) {
|
|
exists(".__ESSENV__.", envir = env, inherits = FALSE)
|
|
}
|