[med-svn] [r-cran-listenv] 01/02: Imported Upstream version 0.6.0
Michael Crusoe
misterc-guest at moszumanska.debian.org
Sat Jun 25 23:50:18 UTC 2016
This is an automated email from the git hooks/post-receive script.
misterc-guest pushed a commit to branch master
in repository r-cran-listenv.
commit c6f21a0ffcd603259935be97b2e29c565233eed7
Author: Michael R. Crusoe <crusoe at ucdavis.edu>
Date: Sat Jun 25 16:14:33 2016 -0700
Imported Upstream version 0.6.0
---
DESCRIPTION | 20 +
MD5 | 31 ++
NAMESPACE | 33 ++
NEWS | 65 +++
R/get_variable.R | 87 ++++
R/listenv.R | 960 ++++++++++++++++++++++++++++++++++++
R/parse_env_subset.R | 269 ++++++++++
R/undim.R | 34 ++
R/utils.R | 33 ++
build/vignette.rds | Bin 0 -> 230 bytes
inst/doc/listenv.html | 642 ++++++++++++++++++++++++
inst/doc/listenv.md.rsp | 360 ++++++++++++++
man/as.list.listenv.Rd | 27 +
man/cash-.listenv.Rd | 22 +
man/cash-set-.listenv.Rd | 21 +
man/get_variable.Rd | 27 +
man/length.listenv.Rd | 16 +
man/listenv.Rd | 33 ++
man/map.Rd | 20 +
man/names.listenv.Rd | 17 +
man/parse_env_subset.Rd | 24 +
man/undim.Rd | 27 +
tests/as.listenv.R | 33 ++
tests/get_variable,dimensions.R | 38 ++
tests/get_variable.R | 100 ++++
tests/listenv,dimensions.R | 303 ++++++++++++
tests/listenv.R | 692 ++++++++++++++++++++++++++
tests/parse_env_subset,dimensions.R | 105 ++++
tests/parse_env_subset.R | 222 +++++++++
tests/undim.R | 33 ++
tests/utils.R | 43 ++
vignettes/listenv.md.rsp | 360 ++++++++++++++
32 files changed, 4697 insertions(+)
diff --git a/DESCRIPTION b/DESCRIPTION
new file mode 100644
index 0000000..4960961
--- /dev/null
+++ b/DESCRIPTION
@@ -0,0 +1,20 @@
+Package: listenv
+Version: 0.6.0
+Depends: R (>= 3.1.2)
+Suggests: R.utils, R.rsp
+VignetteBuilder: R.rsp
+Title: Environments Behaving (Almost) as Lists
+Authors at R: c(person("Henrik", "Bengtsson", role=c("aut", "cre", "cph"),
+ email = "henrikb at braju.com"))
+Description: List environments are environments that have list-like properties. For instance, the elements of a list environment are ordered and can be accessed and iterated over using index subsetting, e.g. 'x <- listenv(a=1, b=2); for (i in seq_along(x)) x[[i]] <- x[[i]]^2; y <- as.list(x)'.
+License: LGPL (>= 2.1)
+LazyLoad: TRUE
+URL: https://github.com/HenrikBengtsson/listenv
+BugReports: https://github.com/HenrikBengtsson/listenv/issues
+RoxygenNote: 5.0.1
+NeedsCompilation: no
+Packaged: 2015-12-27 23:02:54 UTC; hb
+Author: Henrik Bengtsson [aut, cre, cph]
+Maintainer: Henrik Bengtsson <henrikb at braju.com>
+Repository: CRAN
+Date/Publication: 2015-12-28 00:07:26
diff --git a/MD5 b/MD5
new file mode 100644
index 0000000..de276cf
--- /dev/null
+++ b/MD5
@@ -0,0 +1,31 @@
+e745d634585a29c2e525cfb3d39d59e8 *DESCRIPTION
+89e15c945dcfb605128c7abdc0f68f80 *NAMESPACE
+c9f8fda1ddd2984c19a67b0ce7a20210 *NEWS
+f06dc34d2a1839d07cd98d30600e3c26 *R/get_variable.R
+7a9462d34a31c2577bdd583fec4d052a *R/listenv.R
+5fa2a45fdd2adb019d52f36b466cd268 *R/parse_env_subset.R
+fe8db3909366f82cc1d31603046f0e92 *R/undim.R
+0b750b7626b75191087db81e86f7e731 *R/utils.R
+950222ec3362beed946618f55d5254dd *build/vignette.rds
+56d74cee9b8f35a17dd7a98ab0c16e8c *inst/doc/listenv.html
+43ebcd44126cb94d23ac395145ba2444 *inst/doc/listenv.md.rsp
+db64844e07ab14c995f6116fcfe170df *man/as.list.listenv.Rd
+aeff557461299c3b2e5bcd5968991af1 *man/cash-.listenv.Rd
+3764c89fb96ace37631c81fd22a3e638 *man/cash-set-.listenv.Rd
+dcd80183ea3e1c1b87b6785b59536880 *man/get_variable.Rd
+90ca995bbbc8ac59ad49c3202eeed63e *man/length.listenv.Rd
+957163178691b32d4a5987d96abd430c *man/listenv.Rd
+f7a3e638c5ddc9075772ccc2c1604838 *man/map.Rd
+563213dc5b6d707bcc0367a0321c1396 *man/names.listenv.Rd
+d6870fe0e2c4089f7d483db848ab3a74 *man/parse_env_subset.Rd
+cbdcda4780136f1f2a35ed42b39d8f31 *man/undim.Rd
+06b8c0ea74d231f933dd0ccda150594f *tests/as.listenv.R
+384d4562acfd09d1a0e8b0970d01f745 *tests/get_variable,dimensions.R
+13f5f5c5c2cb4965c35f27d628463776 *tests/get_variable.R
+8e40808adef445b921d0edd5b17d8ffa *tests/listenv,dimensions.R
+6a67a4b0a46b2be6452fc02b33956828 *tests/listenv.R
+d256b53faf4e45c2765fc8707dcdc589 *tests/parse_env_subset,dimensions.R
+b76a7090fb365a74664dbb2b1ed63597 *tests/parse_env_subset.R
+07e4d80d68932de4faf92db1e9058d0f *tests/undim.R
+3ff218c62aa9169c129c7d4e5b6c0ec6 *tests/utils.R
+43ebcd44126cb94d23ac395145ba2444 *vignettes/listenv.md.rsp
diff --git a/NAMESPACE b/NAMESPACE
new file mode 100644
index 0000000..48d9993
--- /dev/null
+++ b/NAMESPACE
@@ -0,0 +1,33 @@
+# Generated by roxygen2: do not edit by hand
+
+S3method("$",listenv)
+S3method("$<-",listenv)
+S3method("[",listenv)
+S3method("[<-",listenv)
+S3method("[[",listenv)
+S3method("[[<-",listenv)
+S3method("dim<-",listenv)
+S3method("dimnames<-",listenv)
+S3method("length<-",listenv)
+S3method("names<-",listenv)
+S3method(all.equal,listenv)
+S3method(as.list,listenv)
+S3method(as.listenv,default)
+S3method(as.listenv,environment)
+S3method(as.listenv,list)
+S3method(as.listenv,listenv)
+S3method(dim,listenv)
+S3method(dimnames,listenv)
+S3method(get_variable,listenv)
+S3method(length,listenv)
+S3method(names,listenv)
+S3method(print,listenv)
+S3method(undim,default)
+S3method(undim,listenv)
+S3method(unlist,listenv)
+export(as.listenv)
+export(get_variable)
+export(listenv)
+export(map)
+export(parse_env_subset)
+export(undim)
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..9314196
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,65 @@
+Package: listenv
+================
+
+Version: 0.6.0 [2015-12-27]
+o Added support for multi-dimensional subsetting of list environments
+ just as for list.
+o BUG FIX: parse_env_subset(x[[idx]]) for list environment 'x' and
+ index 'idx' claimed x[[idx]] exists as long as idx in [1,length(x)]
+ but it forgot to check if element really existed, which may not
+ be true if 'x' has been expanded.
+
+
+Version: 0.5.0 [2015-10-30]
+o Add support for assigning elements when creating list environment
+ similarly how to lists work, e.g. x <- listenv(a=1, b=2).
+o length(x) <- n now expand/truncate a list environment.
+o Added unlist() and all.equal() for list environments.
+o DEPRECATED: Deprecated x <- listenv(length=n). Instead use
+ x <- listenv(); length(x) <- n.
+o BUG FIX: as.listenv(x) would drop NULL elements in 'x'.
+o BUG FIX: x[idxs], x[name] <- y and x$<name> <- y would introduce
+ NA names for non-named list environments.
+
+
+Version: 0.4.0 [2015-08-08]
+o Added as.listenv().
+o CONSISTENCY: Assigning NULL now removes element just as lists,
+ e.g. x$a <- NULL. To assign value NULL, do x['a'] <- list(NULL).
+o Added support for subsetting with [(), which returns another
+ list environment, e.g. x[2:3], x[-1] and x[c(TRUE, FALSE)].
+o Added [<- assignment, e.g. x['a'] <- 1 and x[2:3] <- c(3,8).
+o CLEANUP: Dropped stray debug code.
+
+
+Version: 0.3.0 [2015-05-23]
+o Package no longer depends on other packages.
+
+
+Version: 0.2.4 [2015-05-22]
+o Added helper function parse_env_subset().
+
+
+Version: 0.2.3 [2015-05-21]
+o print() on listenv() handles empty and no-named listenv:s better.
+
+
+Version: 0.2.2 [2015-05-20]
+o Now listenv(length=...) always allocates internal variables.
+
+
+Version: 0.2.1 [2015-05-19]
+o get_variable() gained argument 'mustExist'.
+
+
+Version: 0.2.0 [2015-05-19]
+o Moved list environments from an in-house package to its own package.
+
+
+Version: 0.1.4 [2015-05-02]
+o Added print() for listenv:s.
+o CLEANUP: Using tempvar() of R.utils.
+
+
+Version: 0.1.0 [2015-02-07]
+o Created.
diff --git a/R/get_variable.R b/R/get_variable.R
new file mode 100644
index 0000000..c506c04
--- /dev/null
+++ b/R/get_variable.R
@@ -0,0 +1,87 @@
+#' Get name of variable for a specific element of list environment
+#'
+#' @param x A list environment.
+#' @param name The name or index of element of interest.
+#' @param mustExist If TRUE, an error is generated if \code{name}
+#' does not exist.
+#' @param create If TRUE, element \code{name} is created if missing.
+#'
+#' @return The name of the underlying variable
+#'
+#' @aliases get_variable.listenv
+#' @export
+#' @keywords internal
+get_variable <- function(...) UseMethod("get_variable")
+
+#' @export
+get_variable.listenv <- function(x, name, mustExist=FALSE, create=!mustExist, ...) {
+ if (is.character(name)) {
+ } else if (is.numeric(name)) {
+ } else {
+ stop("Subscript must be a name or an index: ", mode(name), call.=FALSE)
+ }
+
+ dim <- dim(x)
+ if (is.null(dim)) {
+ if (length(name) != 1L) {
+ stop("Subscript must be a scalar: ", length(name), call.=FALSE)
+ }
+ } else {
+ ndim <- length(dim)
+ if (length(name) != 1L && length(name) != ndim) {
+ stop(sprintf("Subscript must be a scalar or of equal length as the number of dimension (%d): %d", ndim, length(name)), call.=FALSE)
+ }
+
+ ## Map multi-dimensional index to scalar index
+ if (length(name) > 1L) {
+ stopifnot(is.numeric(name))
+ idxs <- name
+ if (anyNA(idxs)) stop("Unknown index detected")
+
+ for (kk in seq_len(ndim)) {
+ if (idxs[kk] < 1 || idxs[kk] > dim[kk]) {
+ stop(sprintf("Index #%d out of range [1,%d]: %s", kk, dim[kk], idxs[kk]))
+ }
+ }
+ bases <- rev(c(cumprod(dim[-ndim]), 1))
+ idx <- sum(bases * (idxs-1)) + 1
+ name <- idx
+ }
+ }
+
+ map <- map(x)
+
+ ## Existing variable?
+ var <- map[name]
+ if (length(var) == 1L && !is.na(var)) return(var)
+
+ if (mustExist) {
+ stop(sprintf("No such %s element: %s", sQuote(class(x)[1]), name))
+ }
+
+ ## Create new variable
+ if (is.character(name)) {
+ var <- name
+
+ ## Non-existing name?
+ if (!is.element(name, names(map))) {
+ map <- c(map, var)
+ names(map)[length(map)] <- var
+ }
+ } else if (is.numeric(name)) {
+ i <- name
+ ## Expand map?
+ if (i > length(map)) {
+ extra <- rep(NA_character_, times=i-length(map))
+ map <- c(map, extra)
+ }
+ ## Create internal variable
+ var <- new_variable(x, value=NULL, create=create)
+ map[i] <- var
+ }
+
+ ## Update map?
+ if (create) map(x) <- map
+
+ var
+}
diff --git a/R/listenv.R b/R/listenv.R
new file mode 100644
index 0000000..78eae4e
--- /dev/null
+++ b/R/listenv.R
@@ -0,0 +1,960 @@
+#' Create a list environment
+#'
+#' @param \dots (optional) Named and/or unnamed objects to be
+#' assigned to the list environment.
+#'
+#' @return An environment of class `listenv`.
+#'
+#' @example incl/listenv.R
+#'
+#' @aliases as.listenv
+#' @export
+listenv <- function(...) {
+ args <- list(...)
+ nargs <- length(args)
+ names <- names(args)
+
+ ## Allocate empty list environment
+ metaenv <- new.env(parent=parent.frame())
+ env <- new.env(parent=metaenv)
+
+ ## Add elements?
+ if (nargs > 0L) {
+ ## Backward compatibility
+ if (nargs == 1L && identical(names[1L], "length")) {
+ .Deprecated(msg="Use of x <- listenv(length=n) to allocate a list environment of length n is deprecated. Use x <- listenv(); length(x) <- n instead.")
+ length <- args$length
+ stopifnot(length >= 0L)
+ args <- vector("list", length=length)
+ nargs <- length
+ names <- NULL
+ }
+ }
+
+ ## Allocate internal variables
+ maps <- sprintf(".listenv_var_%d", seq_len(nargs))
+ names(maps) <- names
+ for (kk in seq_len(nargs)) {
+ assign(maps[kk], value=args[[kk]], envir=env, inherits=FALSE)
+ }
+ metaenv$.listenv.map <- maps
+
+ assign(".listenv_var_count", nargs, envir=env, inherits=FALSE)
+
+ class(env) <- c("listenv", class(env))
+
+ env
+}
+
+#' @export
+#' @rdname listenv
+as.listenv <- function(...) UseMethod("as.listenv")
+
+#' @export
+as.listenv.listenv <- function(x, ...) {
+ x
+}
+
+#' @export
+as.listenv.list <- function(x, ...) {
+ nx <- length(x)
+ res <- listenv()
+ length(res) <- nx
+ names(res) <- names <- names(x)
+ for (kk in seq_len(nx)) {
+ value <- x[[kk]]
+ if (is.null(value)) value <- list(NULL)
+ res[[kk]] <- value
+ }
+
+ ## Set dimensions?
+ dim <- dim(x)
+ if (!is.null(dim)) {
+ dim(res) <- dim
+ dimnames(res) <- dimnames(x)
+ names(res) <- names
+ }
+
+ res
+}
+
+#' @export
+as.listenv.environment <- function(x, ...) {
+ as.listenv(as.list(x, ...))
+}
+
+#' @export
+as.listenv.default <- function(x, ...) {
+ as.listenv(as.list(x, ...))
+}
+
+
+#' @export
+print.listenv <- function(x, ...) {
+ n <- length(x)
+ dim <- dim(x)
+ ndim <- length(dim)
+ names <- names(x)
+ dimnames <- dimnames(x)
+ class <- class(x)[1L]
+
+ if (ndim <= 1) {
+ what <- "vector"
+ } else if (ndim == 2) {
+ what <- "matrix"
+ } else {
+ what <- "array"
+ }
+
+ s <- sprintf("A %s %s with %d", sQuote(class), what, n)
+ if (is.null(names) && n > 0) {
+ s <- sprintf("%s unnamed", s)
+ }
+ if (n == 1) {
+ s <- sprintf("%s element", s)
+ } else {
+ s <- sprintf("%s elements", s)
+ }
+ if (!is.null(names)) {
+ s <- sprintf("%s (%s)", s, hpaste(sQuote(names)))
+ }
+ if (ndim > 1) {
+ dimstr <- paste(dim, collapse="x")
+ hasDimnames <- !sapply(dimnames, FUN=is.null)
+ dimnamesT <- sapply(dimnames, FUN=function(x) hpaste(sQuote(x)))
+
+ s <- sprintf("%s arranged in %s", s, dimstr)
+
+ if (ndim == 2) {
+ if (is.null(dimnames)) {
+ s <- sprintf("%s unnamed rows and columns", s, dimstr)
+ } else {
+ if (all(hasDimnames)) {
+ s <- sprintf("%s rows (%s) and columns (%s)", s, dimnamesT[1L], dimnamesT[2L])
+ } else if (hasDimnames[1]) {
+ s <- sprintf("%s rows (%s) and unnamed columns", s, dimnamesT[1L])
+ } else if (hasDimnames[2]) {
+ s <- sprintf("%s unnamed rows and columns (%s)", s, dimnamesT[2L])
+ } else {
+ s <- sprintf("%s unnamed rows and columns", s, dimstr)
+ }
+ }
+ } else {
+ if (is.null(dimnames)) {
+ s <- sprintf("%s unnamed dimensions", s)
+ } else {
+ dimnamesT[!hasDimnames] <- "NULL"
+ dimnamesT <- sprintf("#%d: %s", seq_along(dimnamesT), dimnamesT)
+ dimnamesT <- paste(dimnamesT, collapse="; ")
+ if (all(hasDimnames)) {
+ s <- sprintf("%s dimensions (%s)", s, dimnamesT)
+ } else if (!any(hasDimnames)) {
+ s <- sprintf("%s unnamed dimensions", s)
+ } else {
+ s <- sprintf("%s partially named dimensions (%s)", s, dimnamesT)
+ }
+ }
+ }
+ }
+
+ s <- sprintf("%s.\n", s)
+ cat(s)
+}
+
+#' Variable name map for elements of list environment
+#'
+#' @param x A list environment.
+#'
+#' @return The a named character vector
+#'
+#' @aliases map.listenv
+#' @export
+#' @keywords internal
+map <- function(x, ...) {
+ get(".listenv.map", envir=parent.env(x), inherits=FALSE)
+}
+
+`map<-` <- function(x, value) {
+ stopifnot(is.character(value))
+ assign(".listenv.map", value, envir=parent.env(x), inherits=FALSE)
+ invisible(x)
+}
+
+#' Number of elements in list environment
+#'
+#' @param x A list environment.
+#'
+#' @export
+#' @keywords internal
+length.listenv <- function(x) {
+ length(map(x))
+}
+
+#' @export
+`length<-.listenv` <- function(x, value) {
+ map <- map(x)
+ n <- length(map)
+ value <- as.numeric(value)
+
+ if (value < 0) stop("invalid value")
+
+ ## Nothing to do?
+ if (value == n) return(invisible(x))
+
+ ## Expand or shrink?
+ if (value > n) {
+ ## Add place holders for added elements
+ extra <- rep(NA_character_, times=value-n)
+ map <- c(map, extra)
+ } else {
+ ## Drop existing variables
+ drop <- (value+1):n
+ var <- map[drop]
+ ## Some may be internal place holders
+ var <- var[!is.na(var)]
+ if (length(var) > 0) remove(list=var, envir=x, inherits=FALSE)
+ map <- map[-drop]
+ }
+ map(x) <- map
+
+ invisible(x)
+}
+
+
+#' Names of elements in list environment
+#'
+#' @param x A list environment.
+#'
+#' @aliases names<-.listenv
+#' @export
+#' @keywords internal
+names.listenv <- function(x) {
+ names(map(x))
+}
+
+#' @export
+`names<-.listenv` <- function(x, value) {
+ map <- map(x)
+ if (is.null(value)) {
+ } else if (length(value) != length(map)) {
+ stop(sprintf("Number of names does not match the number of elements: %s != %s", length(value), length(map)))
+ }
+## if (any(duplicated(value))) {
+## stop("Environments cannot have duplicate names on elements")
+## }
+ names(map) <- value
+ map(x) <- map
+ invisible(x)
+}
+
+#' List representation of a list environment
+#'
+#' @param x A list environment.
+#' @param all.names If \code{TRUE}, variable names starting with
+#' a period are included, otherwise not.
+#' @param sorted If \code{TRUE}, elements are ordered by their names
+#' before being compared, otherwise not.
+#' @param ... Not used.
+#'
+#' @return A list.
+#'
+#' @export
+#' @keywords internal
+as.list.listenv <- function(x, all.names=TRUE, sorted=FALSE, ...) {
+ vars <- map(x)
+ nvars <- length(vars)
+ names <- names(x)
+
+ ## Drop names starting with a period
+ if (!all.names && nvars > 0) {
+ keep <- !grepl("^[.]", names)
+ vars <- vars[keep]
+ names <- names[keep]
+ nvars <- length(vars)
+ }
+
+ ## Sort by names?
+ if (sorted && nvars > 0) {
+ o <- order(names)
+ vars <- vars[o]
+ names <- names[o]
+ }
+
+ ## Collect as a named list
+ res <- vector("list", length=nvars)
+ names(res) <- names
+
+ if (nvars > 0) {
+ ok <- !is.na(vars)
+ res[ok] <- mget(vars[ok], envir=x, inherits=FALSE)
+ }
+
+ ## Set dimensions?
+ dim <- dim(x)
+ if (!is.null(dim)) {
+ dim(res) <- dim
+ dimnames(res) <- dimnames(x)
+ names(res) <- names
+ }
+
+ res
+}
+
+
+#' Get elements of list environment
+#'
+#' @param x A list environment.
+#' @param name The name or index of the element to retrieve.
+#'
+#' @return The value of an element or NULL if the element does not exist
+#'
+#' @aliases [[.listenv
+#' @aliases [.listenv
+#' @export
+#' @keywords internal
+`$.listenv` <- function(x, name) {
+#' @keywords internal
+ map <- map(x)
+ var <- map[name]
+
+ ## Non-existing variable?
+ if (is.na(var)) return(NULL)
+
+ get(var, envir=x, inherits=FALSE)
+}
+
+
+## [[i,j,...]] -> [[idx]]
+toIndex <- function(x, idxs) {
+ nidxs <- length(idxs)
+
+ dim <- dim(x)
+ if (is.null(dim)) dim <- length(x)
+ ndim <- length(dim)
+ if (ndim != nidxs) {
+ stop("incorrect number of dimensions")
+ }
+ dimnames <- dimnames(x)
+ idxDimnames <- dimnames
+
+ ## Indexing scale factor per dimension
+ scale <- c(1L, cumprod(dim[-ndim]))
+
+ ## Subset
+ idx <- 1
+ for (kk in 1:nidxs) {
+ i <- idxs[[kk]]
+ ni <- length(i)
+ if (is.character(i)) {
+ name <- i
+ i <- match(name, table=dimnames[[kk]])
+ if (anyNA(i)) stop("subscript out of bounds")
+ } else if (is.logical(i)) {
+ d <- dim[kk]
+ ni <- length(i)
+ if (ni > d) stop("(subscript) logical subscript too long")
+ if (ni < d) i <- rep(i, length.out=d)
+ i <- which(i)
+ } else if (is.numeric(i)) {
+ d <- dim[kk]
+ if (any(i > d)) stop("subscript out of bounds")
+ if (any(i < 0)) {
+ if (any(i > 0)) {
+ stop("only 0's may be mixed with negative subscripts")
+ }
+ ## Drop elements
+ i <- setdiff(seq_len(d), -i)
+ }
+ ## Drop zeros
+ i <- i[i != 0]
+ } else {
+ stop("invalid subscript type", sQuote(typeof(i)))
+ }
+
+ ## Subset dimnames?
+ if (!is.null(idxDimnames)) {
+ dn <- idxDimnames[[kk]]
+ if (!is.null(dn)) idxDimnames[[kk]] <- dn[i]
+ }
+
+ i <- scale[kk]*(i - 1)
+ if (kk == 1) {
+ idx <- idx + i
+ } else {
+ idx <- outer(idx, i, FUN=`+`)
+ }
+ } # for (kk ...)
+
+ ## Sanity check
+ dim <- dim(idx)
+ ndim <- length(dim)
+ if (ndim != nidxs) {
+ stop(sprintf("INTERNAL ERROR: Incompatible dimensions: %d != %d", ndim, nidxs))
+ }
+
+ ## Preserve names(dim)
+ names(dim(idx)) <- names(dim(x))
+
+ ## Preserve dimnames
+ dimnames(idx) <- idxDimnames
+
+
+ idx
+} # toIndex()
+
+
+#' @export
+`[[.listenv` <- function(x, ...) {
+ map <- map(x)
+ n <- length(map)
+
+ idxs <- list(...)
+ nidxs <- length(idxs)
+
+ ## Subsetting by multiple dimensions?
+ if (nidxs > 1L) {
+ i <- toIndex(x, idxs)
+ } else {
+ i <- idxs[[1L]]
+ if (is.character(i)) {
+ name <- i
+ i <- match(name, table=names(map))
+ if (is.na(i)) return(NULL)
+ } else if (!is.numeric(i)) {
+ return(NextMethod("[["))
+ }
+
+ if (length(i) != 1L) {
+ stop("Subsetting of more than one element at the time is not allowed for listenv's: ", length(i))
+ }
+
+ if (i < 1L || i > n) {
+ stop(sprintf("Subscript out of bounds [%d,%d]: %d", min(1,n), n, i), call.=FALSE)
+ }
+ }
+
+ var <- map[i]
+
+ ## Return default (NULL)?
+ if (is.na(var) || !exists(var, envir=x, inherits=FALSE)) return(NULL)
+
+ get(var, envir=x, inherits=FALSE)
+}
+
+
+#' @export
+`[.listenv` <- function(x, ..., drop=TRUE) {
+ ## Need to allow for implicit indices, e.g. x[1,,2]
+ idxs <- as.list(sys.call())[-(1:2)]
+ idxs$drop <- NULL
+ nidxs <- length(idxs)
+
+ ## Assert that subsetting has correct shape
+ dim <- dim(x)
+ ndim <- length(dim)
+ if (nidxs > 1 && nidxs != ndim) {
+ stop(sprintf("Incorrect subsetting. Expected %d dimensions but got %d", ndim, nidxs))
+ }
+
+ ## Implicitly specified dimensions
+ missing <- sapply(idxs, FUN=function(x) is.symbol(x) && identical("", deparse(x)))
+ if (any(missing)) {
+ if (nidxs == ndim) {
+ envir <- parent.frame()
+ for (kk in seq_len(ndim)) {
+ if (missing[kk]) {
+ idxs[[kk]] <- seq_len(dim[kk])
+ } else {
+ idxs[[kk]] <- eval(idxs[[kk]], envir=envir)
+ }
+ }
+ } else if (nidxs == 1) {
+ if (ndim == 0) {
+ idxs <- list(seq_len(length(x)))
+ } else {
+ ## Special case: Preserve dimensions when x[]
+ idxs <- lapply(dim, FUN=function(n) seq_len(n))
+ nidxs <- length(idxs)
+ }
+ }
+ } else {
+ envir <- parent.frame()
+ idxs <- lapply(idxs, FUN=eval, envir=envir)
+ }
+
+ if (nidxs <= 1L) {
+ i <- idxs[[1L]]
+ } else {
+ i <- toIndex(x, idxs)
+ }
+
+ map <- map(x)
+ nmap <- length(map)
+ names <- names(map)
+
+ if (is.null(i)) {
+ i <- integer(0L)
+ } else if (is.character(i)) {
+ name <- i
+ i <- match(name, table=names)
+ } else if (is.numeric(i)) {
+ ## Exclude elements with negative indices?
+ if (any(i < 0)) {
+ stopifnot(is.null(dim(i)))
+ if (any(i > 0)) {
+ stop("only 0's may be mixed with negative subscripts")
+ }
+ ## Drop elements
+ i <- setdiff(seq_len(nmap), -i)
+ }
+ ## Drop zeros?
+ if (is.null(dim(i))) {
+ i <- i[i != 0]
+ }
+ } else if (is.logical(i)) {
+ if (length(i) < nmap) i <- rep(i, length.out=nmap)
+ i <- which(i)
+ } else {
+ return(NextMethod("["))
+ }
+
+ ## Nothing to do?
+ ni <- length(i)
+
+ ## Allocate result
+ res <- listenv()
+ length(res) <- ni
+ res <- structure(res, class=class(x))
+
+ if (ni > 0L) {
+ ## Add names?
+ if (!is.null(names)) {
+ names2 <- names[i]
+ names2[i > nmap] <- ""
+ names(res) <- names2
+ }
+
+ ## Ignore out-of-range indices
+ j <- i[i <= nmap]
+ for (kk in seq_along(j)) {
+ value <- x[[j[kk]]]
+ if (!is.null(value)) res[[kk]] <- value
+ }
+ }
+
+ ## Preserve dimensions?
+ dim <- dim(i)
+ ndim <- length(dim)
+ if (ndim > 1) {
+ dimnames <- dimnames(i)
+
+ ## Drop singleton dimensions?
+ if (drop) {
+ keep <- (dim != 1)
+ dim <- dim[keep]
+ dimnames <- dimnames[keep]
+ ndim <- length(dim)
+ }
+
+ if (ndim > 1) {
+ names <- names(res)
+ dim(res) <- dim
+ dimnames(res) <- dimnames
+ names(res) <- names
+ }
+ }
+
+ res
+}
+
+
+new_variable <- function(envir, value, create=TRUE) {
+ count <- get(".listenv_var_count", envir=envir, inherits=FALSE)
+
+ count <- count + 1L
+ name <- sprintf(".listenv_var_%f", count)
+
+ if (!missing(value)) {
+ assign(name, value, envir=envir, inherits=FALSE)
+ }
+
+ if (create) {
+ assign(".listenv_var_count", count, envir=envir, inherits=FALSE)
+ }
+
+ name
+} # new_variable()
+
+
+assign_by_name <- function(x, name, value) {
+ ## Argument 'name':
+ if (length(name) == 0L) {
+ stop("Cannot assign value. Zero-length name.", call.=FALSE)
+ } else if (length(name) > 1L) {
+ stop("Cannot assign value. More than one name specified: ", hpaste(name), call.=FALSE)
+ } else if (nchar(name) == 0L) {
+ stop("Cannot assign value. Empty name specific: ", name, call.=FALSE)
+ }
+
+ map <- map(x)
+ names <- names(map)
+
+ ## Map to an existing or a new element?
+ if (is.element(name, names)) {
+ var <- map[name]
+
+ ## A new variable?
+ if (is.na(var)) {
+ var <- name
+ map[name] <- name
+ map(x) <- map
+ }
+ } else {
+ var <- name
+
+ ## Append to map
+ map <- c(map, var)
+ if (is.null(names)) names <- rep("", times=length(map))
+ names[length(map)] <- var
+ names(map) <- names
+ map(x) <- map
+ }
+
+ ## Assign value
+ assign(var, value, envir=x, inherits=FALSE)
+
+ invisible(x)
+} # assign_by_name()
+
+
+assign_by_index <- function(x, i, value) {
+ ## Argument 'i':
+ if (length(i) == 0L) {
+ stop("Cannot assign value. Zero-length index.", call.=FALSE)
+ } else if (length(i) > 1L) {
+ stop("Cannot assign value. More than one index specified: ", hpaste(i), call.=FALSE)
+ } else if (!is.finite(i)) {
+ stop("Cannot assign value. Non-finite index: ", i, call.=FALSE)
+ } else if (i < 1L) {
+ stop("Cannot assign value. Non-positive index: ", i, call.=FALSE)
+ }
+
+ map <- map(x)
+ n <- length(map)
+
+ ## Variable name
+ var <- map[i]
+
+ ## Non-existing variable?
+ if (is.na(var)) {
+ ## Expand map?
+ if (i > n) {
+ extra <- rep(NA_character_, times=i-n)
+ map <- c(map, extra)
+ }
+
+ ## Create internal variable
+ map[i] <- new_variable(x, value=value)
+
+ ## Update map
+ map(x) <- map
+ } else {
+ assign(var, value, envir=x, inherits=FALSE)
+ }
+
+ invisible(x)
+} # assign_by_index()
+
+
+remove_by_name <- function(x, name) {
+ ## Argument 'name':
+ if (length(name) == 0L) {
+ stop("Cannot remove element. Zero-length name.", call.=FALSE)
+ } else if (length(name) > 1L) {
+ stop("Cannot remove element. More than one name specified: ", hpaste(name), call.=FALSE)
+ } else if (nchar(name) == 0L) {
+ stop("Cannot remove element. Empty name specific: ", name, call.=FALSE)
+ }
+
+ map <- map(x)
+
+ ## Position in names map?
+ idx <- match(name, names(map))
+
+ ## Nothing to do?
+ if (is.na(idx)) return(invisible(x))
+
+ ## Drop internal variable, unless place holder
+ var <- map[idx]
+ if (!is.na(var)) remove(list=var, envir=x, inherits=FALSE)
+
+ map <- map[-idx]
+ map(x) <- map
+
+ invisible(x)
+} # remove_by_name()
+
+
+remove_by_index <- function(x, i) {
+ ## Argument 'i':
+ if (length(i) == 0L) {
+ stop("Cannot remove element. Zero-length index.", call.=FALSE)
+ } else if (length(i) > 1L) {
+ stop("Cannot remove element. More than one index specified: ", hpaste(i), call.=FALSE)
+ } else if (!is.finite(i)) {
+ stop("Cannot remove element. Non-finite index: ", i, call.=FALSE)
+ } else if (i < 1L) {
+ stop("Cannot remove element. Non-positive index: ", i, call.=FALSE)
+ }
+
+ map <- map(x)
+
+ ## Nothing to do?
+ if (i > length(map)) return(invisible(x))
+
+ ## Drop internal variable, unless place holder
+ var <- map[i]
+ if (!is.na(var)) remove(list=var, envir=x, inherits=FALSE)
+
+ map <- map[-i]
+ map(x) <- map
+
+ invisible(x)
+} # remove_by_index()
+
+
+
+
+#' Set an element of list environment
+#'
+#' @param x A list environment.
+#' @param name Name or index of element
+#' @param value The value to assign to the element
+#'
+#' @aliases [[<-.listenv
+#' @aliases [<-.listenv
+#' @export
+#' @keywords internal
+`$<-.listenv` <- function(x, name, value) {
+ if (is.null(value)) {
+ remove_by_name(x, name=name)
+ } else {
+ assign_by_name(x, name=name, value=value)
+ }
+}
+
+#' @export
+`[[<-.listenv` <- function(x, ..., value) {
+ map <- map(x)
+ n <- length(map)
+
+ idxs <- list(...)
+ nidxs <- length(idxs)
+
+ ## Subsetting by multiple dimensions?
+ if (nidxs > 1L) {
+ i <- toIndex(x, idxs)
+ } else {
+ i <- idxs[[1L]]
+ if (is.character(i)) {
+ if (is.null(value)) {
+ x <- remove_by_name(x, name=i)
+ } else {
+ x <- assign_by_name(x, name=i, value=value)
+ }
+ return(invisible(x))
+ }
+ }
+
+ if (is.numeric(i)) {
+ if (is.null(value)) {
+ x <- remove_by_index(x, i=i)
+ } else {
+ x <- assign_by_index(x, i=i, value=value)
+ }
+ } else {
+ stop(sprintf("Subsetted [[<- assignment to listenv's is only supported for names and indices, not %s", mode(i)), call.=FALSE)
+ }
+
+ return(invisible(x))
+}
+
+
+#' @export
+`[<-.listenv` <- function(x, ..., value) {
+ ## Need to allow for implicit indices, e.g. x[1,,2]
+ idxs <- as.list(sys.call())[-(1:2)]
+ idxs$value <- NULL
+ nidxs <- length(idxs)
+
+ ## Assert that subsetting has correct shape
+ dim <- dim(x)
+ ndim <- length(dim)
+ if (nidxs > 1 && nidxs != ndim) {
+ stop(sprintf("Incorrect subsetting. Expected %d dimensions but got %d", ndim, nidxs))
+ }
+
+ ## Implicitly specified dimensions
+ missing <- sapply(idxs, FUN=function(x) is.symbol(x) && identical("", deparse(x)))
+ if (any(missing)) {
+ if (nidxs == ndim) {
+ envir <- parent.frame()
+ for (kk in seq_len(ndim)) {
+ if (missing[kk]) {
+ idxs[[kk]] <- seq_len(dim[kk])
+ } else {
+ idxs[[kk]] <- eval(idxs[[kk]], envir=envir)
+ }
+ }
+ } else if (nidxs == 1) {
+ if (ndim == 0) {
+ idxs <- list(seq_len(length(x)))
+ } else {
+ ## Special case: Preserve dimensions when x[]
+ idxs <- lapply(dim, FUN=function(n) seq_len(n))
+ nidxs <- length(idxs)
+ }
+ }
+ } else {
+ envir <- parent.frame()
+ idxs <- lapply(idxs, FUN=eval, envir=envir)
+ }
+
+ if (nidxs <= 1L) {
+ i <- idxs[[1L]]
+ } else {
+ i <- toIndex(x, idxs)
+ }
+
+ ni <- length(i)
+ if (is.logical(i)) {
+ n <- length(x)
+ if (ni < n) i <- rep(i, length.out=n)
+ i <- which(i)
+ ni <- length(i)
+ }
+
+
+ # Nothing to do?
+ if (ni == 0L) return(invisible(x))
+
+ nvalue <- length(value)
+ if (nvalue == 0L) stop("Replacement has zero length", call.=FALSE)
+
+ if (ni != nvalue) {
+ if (ni < nvalue || ni %% nvalue != 0) {
+ warning(sprintf("Number of items to replace is not a multiple of replacement length: %d != %d", ni, nvalue), call.=FALSE)
+ }
+ value <- rep(value, length.out=ni)
+ nvalue <- length(value)
+ }
+
+ if (is.character(i)) {
+ for (kk in seq_len(ni)) {
+ x <- assign_by_name(x, name=i[kk], value=value[[kk]])
+ }
+ } else if (is.numeric(i)) {
+ for (kk in seq_len(ni)) {
+ x <- assign_by_index(x, i=i[kk], value=value[[kk]])
+ }
+ } else {
+ stop(sprintf("Subsetted [<- assignment to listenv's is only supported for names and indices, not %s", mode(i)), call.=FALSE)
+ }
+ return(invisible(x))
+}
+
+
+#' @export
+#' @method unlist listenv
+unlist.listenv <- function(x, recursive=TRUE, use.names=TRUE) {
+ names <- names(x)
+ x <- as.list(x)
+ names(x) <- names
+
+ if (recursive) {
+ repeat {
+ x <- unlist(x, recursive=TRUE, use.names=use.names)
+ idxs <- unlist(lapply(x, FUN=inherits, "listenv"), use.names=FALSE)
+ if (length(idxs) == 0L) break
+ idxs <- which(idxs)
+ if (length(idxs) == 0L) break
+ for (ii in idxs) {
+ x[[ii]] <- unlist(x[[ii]], recursive=TRUE, use.names=use.names)
+ }
+ }
+ x
+ } else {
+ unlist(x, recursive=FALSE, use.names=use.names)
+ }
+}
+
+#' @export
+dim.listenv <- function(x) attr(x, "dim.")
+
+#' @export
+`dim<-.listenv` <- function(x, value) {
+ n <- length(x)
+ if (!is.null(value)) {
+ names <- names(value)
+ value <- as.integer(value)
+ p <- prod(as.double(value))
+ if (p != n) {
+ stop(sprintf("dims [product %d] do not match the length of object [%d]", p, n))
+ }
+ names(value) <- names
+ }
+
+ ## Always remove "dimnames" and "names" attributes, cf. help("dim")
+ dimnames(x) <- NULL
+ names(x) <- NULL
+
+ attr(x, "dim.") <- value
+ x
+}
+
+
+#' @export
+dimnames.listenv <- function(x) attr(x, "dimnames.")
+
+#' @export
+`dimnames<-.listenv` <- function(x, value) {
+ dim <- dim(x)
+ if (is.null(dim) && !is.null(value)) {
+ stop("'dimnames' applied to non-array")
+ }
+ for (kk in seq_along(dim)) {
+ names <- value[[kk]]
+ if (is.null(names)) next
+ n <- length(names)
+ if (n != dim[kk]) {
+ stop(sprintf("length of 'dimnames' [%d] not equal to array extent", kk))
+ }
+ }
+ attr(x, "dimnames.") <- value
+ x
+}
+
+#' @export
+#' @method all.equal listenv
+all.equal.listenv <- function(target, current, all.names=TRUE, sorted=FALSE, ...) {
+ if (identical(target, current)) return(TRUE)
+
+ ## Coerce to lists
+ target <- as.list(target, all.names=all.names, sorted=sorted)
+ current <- as.list(current, all.names=all.names, sorted=sorted)
+
+ ## Not all as.list() methods support 'all.names'
+ if (!all.names) {
+ keep <-
+ target <- target[!grepl("^[.]", names(target))]
+ current <- current[!grepl("^[.]", names(current))]
+ }
+
+ ## Not all as.list() methods support 'sorted'
+ if (sorted) {
+ target <- target[order(names(target))]
+ current <- current[order(names(current))]
+ }
+
+ all.equal(target=target, current=current, ...)
+}
diff --git a/R/parse_env_subset.R b/R/parse_env_subset.R
new file mode 100644
index 0000000..3a02d36
--- /dev/null
+++ b/R/parse_env_subset.R
@@ -0,0 +1,269 @@
+#' Helper function to infer target from expression and environment
+#'
+#' @param expr An expression.
+#' @param envir An environment.
+#' @param substitute If TRUE, then the expression is
+#' \code{substitute()}:ed, otherwise not.
+#'
+#' @return A named list.
+#'
+#' @export
+#' @keywords internal
+parse_env_subset <- function(expr, envir=parent.frame(), substitute=TRUE) {
+ if (substitute) expr <- substitute(expr)
+ code <- paste(deparse(expr), collapse="")
+
+ res <- list(envir=envir, name="", op=NULL, subset=NULL, idx=NA_integer_, exists=NA, code=code)
+
+ if (is.symbol(expr)) {
+ ## Variable specified as a symbol
+ res$name <- deparse(expr)
+ } else if (is.character(expr)) {
+ ## Variable specified as a name
+ if (length(expr) > 1L) {
+ stop(sprintf("Does not specify a single variable, but %d: %s", length(expr), hpaste(sQuote(expr), collapse=", ")), call.=FALSE)
+ }
+ res$name <- expr
+ } else if (is.numeric(expr)) {
+ ## Variable specified as a subset of envir
+ if (length(expr) > 1L) {
+ stop(sprintf("Does not specify a single index, but %d: %s", length(expr), hpaste(sQuote(expr), collapse=", ")), call.=FALSE)
+ }
+ res$subset <- list(expr)
+ } else {
+ n <- length(expr)
+ stopifnot(n >= 2L)
+
+ if (n >= 3L) {
+ ## Assignment to enviroment via $ and [[
+ op <- as.character(expr[[1]])
+ res$op <- op
+ if (op == "$" && n > 3L) {
+ stop("Invalid syntax: ", sQuote(code), call.=FALSE)
+ } else if (!is.element(op, c("$", "[[", "["))) {
+ stop("Invalid syntax: ", sQuote(code), call.=FALSE)
+ }
+
+ ## Target
+ objname <- deparse(expr[[2]])
+ if (!exists(objname, envir=envir, inherits=TRUE)) {
+ stop(sprintf("Object %s not found: %s", sQuote(objname), sQuote(code)), call.=FALSE)
+ }
+
+ obj <- get(objname, envir=envir, inherits=TRUE)
+ if (!is.environment(obj)) {
+ stop(sprintf("Subsetting can not be done on a %s; only to an environment: %s", sQuote(mode(obj)), sQuote(code)), call.=FALSE)
+ }
+ res$envir <- obj
+
+ ## Subset
+ subset <- list()
+ for (kk in 3:n) {
+ missing <- (length(expr[[kk]]) == 1L) && (expr[[kk]] == "")
+ if (missing) {
+ subsetKK <- NULL
+ } else {
+ subsetKK <- expr[[kk]]
+ }
+ if (is.symbol(subsetKK)) {
+ subsetKK <- deparse(subsetKK)
+ if (op == "[[") {
+ if (!exists(subsetKK, envir=envir, inherits=TRUE)) {
+ stop(sprintf("Object %s not found: %s", sQuote(subsetKK), sQuote(code)), call.=FALSE)
+ }
+ subsetKK <- get(subsetKK, envir=envir, inherits=TRUE)
+ }
+ } else if (is.language(subsetKK)) {
+ subsetKK <- eval(subsetKK, envir=envir)
+ }
+ if (is.null(subsetKK)) {
+ subset[kk-2L] <- list(NULL)
+ } else {
+ subset[[kk-2L]] <- subsetKK
+ }
+ }
+
+ res$subset <- subset
+ } # if (n >= 3)
+ } # if (is.symbol(expr))
+
+
+ ## Validat name, iff any
+ name <- res$name
+ if (nzchar(name) && !grepl("^[.a-zA-Z]+", name)) stop("Not a valid variable name: ", sQuote(name), call.=FALSE)
+
+
+ ## Validate subsetting, e.g. x[[1]], x[["a"]], and x$a, iff any
+ subset <- res$subset
+ if (!is.null(subset)) {
+ if (!is.list(subset)) {
+ stop(sprintf("INTERNAL ERROR (expected 'subset' to be a list): %s", sQuote(code)), call.=FALSE)
+ }
+ if (length(subset) == 0L) {
+ stop(sprintf("Subsetting of at least on element is required: %s", sQuote(code)), call.=FALSE)
+ }
+
+ for (kk in seq_along(subset)) {
+ subsetKK <- subset[[kk]]
+ if (is.null(subsetKK)) {
+ } else if (any(is.na(subsetKK))) {
+ stop(sprintf("Invalid subsetting. Subset must not contain missing values: %s", sQuote(code)), call.=FALSE)
+ } else if (is.character(subsetKK)) {
+ if (!all(nzchar(subsetKK))) {
+ stop(sprintf("Invalid subset. Subset must not contain empty names: %s", sQuote(code)), call.=FALSE)
+ }
+ } else if (is.numeric(subsetKK)) {
+ } else {
+ stop(sprintf("Invalid subset of type %s: %s", sQuote(typeof(subsetKK)), sQuote(code)), call.=FALSE)
+ }
+ } # for (kk ...)
+
+ ## Special: listenv:s
+ envir <- res$envir
+ stopifnot(is.environment(envir))
+
+ if (inherits(envir, "listenv")) {
+ names <- names(envir)
+ map <- map(envir)
+ dim <- dim(envir)
+
+ op <- res$op
+ if (is.null(op)) op <- "[["
+
+ ## Multi-dimensional subsetting?
+ if (length(subset) > 1L) {
+ if (is.null(dim)) {
+ stop("Multi-dimensional subsetting on list environment without dimensions: ", sQuote(code), call.=TRUE)
+ }
+ dimnames <- dimnames(envir)
+ exists <- TRUE
+ for (kk in seq_along(subset)) {
+ subsetKK <- subset[[kk]]
+ if (is.null(subsetKK)) {
+ subset[[kk]] <- seq_len(dim[kk])
+ } else if (is.numeric(subsetKK)) {
+ exists <- exists && (subsetKK >= 1 && subsetKK <= dim[kk])
+ } else if (is.character(subsetKK)) {
+ subsetKK <- match(subsetKK, dimnames[[kk]])
+ exists <- exists && !is.na(subsetKK)
+ subset[[kk]] <- subsetKK
+ }
+ }
+
+ ## Indexing scale factor per dimension
+ ndim <- length(dim)
+ scale <- c(1L, cumprod(dim[-ndim]))
+ idx <- 1
+ for (kk in seq_along(subset)) {
+ i <- subset[[kk]]
+ stopifnot(is.numeric(i))
+ d <- dim[kk]
+ if (any(i < 0)) {
+ if (op == "[[") {
+ stop("Invalid (negative) indices: ", hpaste(i))
+ } else if (any(i > 0)) {
+ stop("only 0's may be mixed with negative subscripts")
+ }
+ ## Drop elements
+ i <- setdiff(seq_len(d), -i)
+ }
+ if (any(i > d)) i[i > d] <- NA_integer_
+ ## Drop zeros
+ i <- i[i != 0]
+ i <- scale[kk]*(i - 1)
+ if (kk == 1) {
+ idx <- idx + i
+ } else {
+ idx <- outer(idx, i, FUN=`+`)
+ }
+ } # for (kk ...)
+
+ res$idx <- idx
+ res$name <- names[res$idx]
+ if (length(res$name) == 0L) res$name <- ""
+ if (exists) {
+ exists <- !is.na(map[idx])
+ }
+ res$exists <- exists
+ } else {
+ subset <- subset[[1L]]
+ if (is.numeric(subset)) {
+ i <- subset
+ n <- length(envir)
+ if (any(i < 0)) {
+ if (op == "[[") {
+ stop("Invalid (negative) indices: ", hpaste(i))
+ } else if (any(i > 0)) {
+ stop("only 0's may be mixed with negative subscripts")
+ }
+ ## Drop elements
+ i <- setdiff(seq_len(n), -i)
+ }
+ ## Drop zeros?
+ keep <- which(i != 0)
+ if (length(keep) != length(i)) {
+ if (op == "[[") {
+ ## BACKWARD COMPATIBILITY:
+ ## In order not to break two `R CMD check` package tests
+ ## for future 0.9.0 on CRAN, we tweak the result here in
+ ## order for those two tests not to fail. /HB 2015-12-26
+ ## FIX ME: Remove when future (> 0.9.0) is on CRAN.
+ if (identical(i, 0) && identical(code, "x[[0]]") && is.element("package:future", search()) && utils::packageVersion("future") <= "0.9.0") {
+ res$idx <- i
+ res$exists <- FALSE
+ return(res)
+ }
+ stop("Invalid (zero) indices: ", hpaste(i))
+ }
+ i <- i[keep]
+ }
+ res$idx <- i
+ res$exists <- !is.na(map[res$idx]) & (res$idx >= 1 & res$idx <= n)
+ res$name <- names[i]
+ if (length(res$name) == 0L) res$name <- ""
+ } else if (is.character(subset)) {
+ res$idx <- match(subset, names)
+ res$exists <- !is.na(res$idx) && !is.na(map[res$idx])
+ }
+ }
+ } else {
+ if (length(subset) > 1L) {
+ stop("Invalid subset: ", sQuote(code), call.=TRUE)
+ }
+ subset <- subset[[1L]]
+ }
+ if (is.character(subset)) res$name <- subset
+ }
+
+ ## Identify index?
+ if (inherits(res$envir, "listenv")) {
+ envir <- res$envir
+ if (any(is.na(res$idx)) && nzchar(res$name)) {
+ res$idx <- match(res$name, names(envir))
+ }
+ res$exists <- !is.na(res$idx) & !is.na(map(envir)[res$idx])
+ }
+
+ ## Validate
+ if (is.null(dim) && length(res$subset) == 1 && identical(res$op, "[")) {
+ if (any(is.na(res$idx)) && !nzchar(res$name)) {
+ stop("Invalid subset: ", sQuote(code), call.=TRUE)
+ }
+ }
+
+ unknown <- which(is.na(res$exists))
+ if (length(unknown) > 0) {
+ res$exists[unknown] <- sapply(unknown, FUN=function(idx) {
+ exists(res$name[idx], envir=res$envir, inherits=TRUE)
+ })
+ }
+
+ ## Sanity check
+ stopifnot(is.environment(res$envir))
+ stopifnot(is.character(res$name))
+ stopifnot(is.null(res$idx) || all(is.numeric(res$idx)))
+ stopifnot(is.logical(res$exists), !anyNA(res$exists))
+ stopifnot(length(res$exists) == length(res$idx))
+
+ res
+}
diff --git a/R/undim.R b/R/undim.R
new file mode 100644
index 0000000..1bfef77
--- /dev/null
+++ b/R/undim.R
@@ -0,0 +1,34 @@
+#' Removes the dimension of an object
+#'
+#' @param x An object with or without dimensions
+#' @param ... Not used.
+#'
+#' @return The object with the dimension attribute removed.
+#'
+#' @details
+#' This function does \code{attr(x, "dim") <- NULL}, which
+#' automatically also does \code{attr(x, "dimnames") <- NULL}.
+#' However, other attributes such as names attributes are preserved,
+#' which is not the case if one do \code{dim(x) <- NULL}.
+#'
+#' @export
+#' @aliases undim.default
+#' @aliases undim.listenv
+undim <- function(x, ...) UseMethod("undim")
+
+#' @export
+undim.default <- function(x, ...) {
+ if (is.null(dim(x))) return(x)
+ attr(x, "dim") <- NULL
+ ## Dimnames seems to be unset above, but in case it changes ...
+ attr(x, "dimnames") <- NULL
+ x
+}
+
+#' @export
+undim.listenv <- function(x, ...) {
+ x <- NextMethod("undim")
+ attr(x, "dim.") <- NULL
+ attr(x, "dimnames.") <- NULL
+ x
+}
diff --git a/R/utils.R b/R/utils.R
new file mode 100644
index 0000000..937423e
--- /dev/null
+++ b/R/utils.R
@@ -0,0 +1,33 @@
+## From R.utils 2.0.2 (2015-05-23)
+hpaste <- function(..., sep="", collapse=", ", lastCollapse=NULL, maxHead=if (missing(lastCollapse)) 3 else Inf, maxTail=if (is.finite(maxHead)) 1 else Inf, abbreviate="...") {
+ maxHead <- as.double(maxHead)
+ maxTail <- as.double(maxTail)
+ if (is.null(lastCollapse)) lastCollapse <- collapse
+
+ # Build vector 'x'
+ x <- paste(..., sep=sep)
+ n <- length(x)
+
+ # Nothing todo?
+ if (n == 0) return(x)
+ if (is.null(collapse)) return(x)
+
+ # Abbreviate?
+ if (n > maxHead + maxTail + 1) {
+ head <- x[seq(length=maxHead)]
+ tail <- rev(rev(x)[seq(length=maxTail)])
+ x <- c(head, abbreviate, tail)
+ n <- length(x)
+ }
+
+ if (!is.null(collapse) && n > 1) {
+ if (lastCollapse == collapse) {
+ x <- paste(x, collapse=collapse)
+ } else {
+ xT <- paste(x[1:(n-1)], collapse=collapse)
+ x <- paste(xT, x[n], sep=lastCollapse)
+ }
+ }
+
+ x
+} # hpaste()
diff --git a/build/vignette.rds b/build/vignette.rds
new file mode 100644
index 0000000..bd092ae
Binary files /dev/null and b/build/vignette.rds differ
diff --git a/inst/doc/listenv.html b/inst/doc/listenv.html
new file mode 100644
index 0000000..de3fd86
--- /dev/null
+++ b/inst/doc/listenv.html
@@ -0,0 +1,642 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+
+<title>List Environments</title>
+
+<script type="text/javascript">
+window.onload = function() {
+ var imgs = document.getElementsByTagName('img'), i, img;
+ for (i = 0; i < imgs.length; i++) {
+ img = imgs[i];
+ // center an image if it is the only element of its parent
+ if (img.parentElement.childElementCount === 1)
+ img.parentElement.style.textAlign = 'center';
+ }
+};
+</script>
+
+<!-- Styles for R syntax highlighter -->
+<style type="text/css">
+ pre .operator,
+ pre .paren {
+ color: rgb(104, 118, 135)
+ }
+
+ pre .literal {
+ color: #990073
+ }
+
+ pre .number {
+ color: #099;
+ }
+
+ pre .comment {
+ color: #998;
+ font-style: italic
+ }
+
+ pre .keyword {
+ color: #900;
+ font-weight: bold
+ }
+
+ pre .identifier {
+ color: rgb(0, 0, 0);
+ }
+
+ pre .string {
+ color: #d14;
+ }
+</style>
+
+<!-- R syntax highlighter -->
+<script type="text/javascript">
+var hljs=new function(){function m(p){return p.replace(/&/gm,"&").replace(/</gm,"<")}function f(r,q,p){return RegExp(q,"m"+(r.cI?"i":"")+(p?"g":""))}function b(r){for(var p=0;p<r.childNodes.length;p++){var q=r.childNodes[p];if(q.nodeName=="CODE"){return q}if(!(q.nodeType==3&&q.nodeValue.match(/\s+/))){break}}}function h(t,s){var p="";for(var r=0;r<t.childNodes.length;r++){if(t.childNodes[r].nodeType==3){var q=t.childNodes[r].nodeValue;if(s){q=q.replace(/\n/g,"")}p+=q}else{if(t.chi [...]
+hljs.initHighlightingOnLoad();
+</script>
+
+
+
+<style type="text/css">
+body, td {
+ font-family: sans-serif;
+ background-color: white;
+ font-size: 13px;
+}
+
+body {
+ max-width: 800px;
+ margin: auto;
+ padding: 1em;
+ line-height: 20px;
+}
+
+tt, code, pre {
+ font-family: 'DejaVu Sans Mono', 'Droid Sans Mono', 'Lucida Console', Consolas, Monaco, monospace;
+}
+
+h1 {
+ font-size:2.2em;
+}
+
+h2 {
+ font-size:1.8em;
+}
+
+h3 {
+ font-size:1.4em;
+}
+
+h4 {
+ font-size:1.0em;
+}
+
+h5 {
+ font-size:0.9em;
+}
+
+h6 {
+ font-size:0.8em;
+}
+
+a:visited {
+ color: rgb(50%, 0%, 50%);
+}
+
+pre, img {
+ max-width: 100%;
+}
+pre {
+ overflow-x: auto;
+}
+pre code {
+ display: block; padding: 0.5em;
+}
+
+code {
+ font-size: 92%;
+ border: 1px solid #ccc;
+}
+
+code[class] {
+ background-color: #F8F8F8;
+}
+
+table, td, th {
+ border: none;
+}
+
+blockquote {
+ color:#666666;
+ margin:0;
+ padding-left: 1em;
+ border-left: 0.5em #EEE solid;
+}
+
+hr {
+ height: 0px;
+ border-bottom: none;
+ border-top-width: thin;
+ border-top-style: dotted;
+ border-top-color: #999999;
+}
+
+ at media print {
+ * {
+ background: transparent !important;
+ color: black !important;
+ filter:none !important;
+ -ms-filter: none !important;
+ }
+
+ body {
+ font-size:12pt;
+ max-width:100%;
+ }
+
+ a, a:visited {
+ text-decoration: underline;
+ }
+
+ hr {
+ visibility: hidden;
+ page-break-before: always;
+ }
+
+ pre, blockquote {
+ padding-right: 1em;
+ page-break-inside: avoid;
+ }
+
+ tr, img {
+ page-break-inside: avoid;
+ }
+
+ img {
+ max-width: 100% !important;
+ }
+
+ @page :left {
+ margin: 15mm 20mm 15mm 10mm;
+ }
+
+ @page :right {
+ margin: 15mm 10mm 15mm 20mm;
+ }
+
+ p, h2, h3 {
+ orphans: 3; widows: 3;
+ }
+
+ h2, h3 {
+ page-break-after: avoid;
+ }
+}
+</style>
+
+<meta name="keywords" content="R, package, vignette, listenv">
+<meta name="author" content="Henrik Bengtsson">
+
+</head>
+
+<body>
+<h1>List Environments</h1>
+
+<p><em>List environments</em> are environments that have list-like properties. They are implemented by the <a href="http://cran.r-project.org/package=listenv">listenv</a> package. The main features of a list environment are summarized in the below table:</p>
+
+<table><thead>
+<tr>
+<th>Property</th>
+<th align="center">list environments</th>
+<th align="center">lists</th>
+<th align="center">environments</th>
+</tr>
+</thead><tbody>
+<tr>
+<td>Number of elements, e.g. <code>length()</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+</tr>
+<tr>
+<td>Named elements, e.g. <code>names()</code>, <code>x$a</code> and <code>x[["a"]]</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+</tr>
+<tr>
+<td>Duplicated names</td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Indexed elements, e.g. <code>x[[4]]</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Dimensions, e.g. <code>dim(x)</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Names of dimensions, e.g. <code>dimnames(x)</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Indexing by dimensions, e.g. <code>x[[2,4]]</code> and <code>x[[2,"D"]]</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Multi-element subsetting, e.g. <code>x[c("a", "c")]</code>, <code>x[-1]</code>, <code>[2:1,,3]</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Multi-element subsetting preserves element names</td>
+<td align="center">yes</td>
+<td align="center"></td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Removing element by assigning NULL, e.g. <code>x$c <- NULL</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Mutable, e.g. <code>y <- x; y$a <- 3; identical(y, x)</code></td>
+<td align="center">yes</td>
+<td align="center"></td>
+<td align="center">yes</td>
+</tr>
+<tr>
+<td>Compatible* with <code>assign()</code>, <code>delayedAssign()</code>, <code>get()</code> and <code>exists()</code></td>
+<td align="center">yes</td>
+<td align="center"></td>
+<td align="center">yes</td>
+</tr>
+</tbody></table>
+
+<p>For example,</p>
+
+<pre><code class="r">> x <- listenv(a = 1, b = 2, c = "hello")
+> x
+A 'listenv' vector with 3 elements ('a', 'b', 'c').
+> length(x)
+[1] 3
+> names(x)
+[1] "a" "b" "c"
+> x$a
+[1] 1
+> x[[3]] <- toupper(x[[3]])
+> x$c
+[1] "HELLO"
+> y <- x
+> y$d <- y$a + y[["b"]]
+> names(y)[2] <- "a"
+> y$a
+[1] 1
+> y
+A 'listenv' vector with 4 elements ('a', 'a', 'c', 'd').
+> identical(y, x)
+[1] TRUE
+> for (ii in seq_along(x)) {
++ cat(sprintf("Element %d (%s): %s\n", ii, sQuote(names(x)[ii]),
++ x[[ii]]))
++ }
+Element 1 ('a'): 1
+Element 2 ('a'): 2
+Element 3 ('c'): HELLO
+Element 4 ('d'): 3
+> x[c(1, 3)] <- list(2, "Hello world!")
+> x
+A 'listenv' vector with 4 elements ('a', 'a', 'c', 'd').
+> y <- as.list(x)
+> str(y)
+List of 4
+ $ a: num 2
+ $ a: num 2
+ $ c: chr "Hello world!"
+ $ d: num 3
+> z <- as.listenv(y)
+> z
+A 'listenv' vector with 4 elements ('a', 'a', 'c', 'd').
+> identical(z, x)
+[1] FALSE
+> all.equal(z, x)
+[1] TRUE
+</code></pre>
+
+<h2>Creating list environments</h2>
+
+<p>List environments are created similarly to lists but also similarly to environments. To create an empty list environment, use</p>
+
+<pre><code class="r">> x <- listenv()
+> x
+A 'listenv' vector with 0 elements.
+</code></pre>
+
+<p>This can later can be populated using named assignments,</p>
+
+<pre><code class="r">> x$a <- 1
+> x
+A 'listenv' vector with 1 element ('a').
+</code></pre>
+
+<p>comparable to how both lists and environments work. Similarly to lists, they can also be populated using indices, e.g.</p>
+
+<pre><code class="r">> x[[2]] <- 2
+> x$c <- 3
+> x
+A 'listenv' vector with 3 elements ('a', '', 'c').
+</code></pre>
+
+<p>Just as for lists, a list environment is expanded with <code>NULL</code> elements whenever a new element is added that is beyond the current length plus one, e.g.</p>
+
+<pre><code class="r">> x[[5]] <- 5
+> x
+A 'listenv' vector with 5 elements ('a', '', 'c', '', '').
+> x[[4]]
+NULL
+</code></pre>
+
+<p>As with lists, the above list environment can also be created from the start, e.g.</p>
+
+<pre><code class="r">> x <- listenv(a = 1, 3, c = 4, NULL, 5)
+> x
+A 'listenv' vector with 5 elements ('a', '', 'c', '', '').
+</code></pre>
+
+<p>As for lists, the length of a list environment can at any time be increased or decreased by assigning it a new length.
+If decreased, elements are dropped, e.g.</p>
+
+<pre><code class="r">> x
+A 'listenv' vector with 5 elements ('a', '', 'c', '', '').
+> length(x) <- 2
+> x
+A 'listenv' vector with 2 elements ('a', '').
+> x[[1]]
+[1] 1
+> x[[2]]
+[1] 3
+</code></pre>
+
+<p>If increased, new elements are populated with unnamed elements of <code>NULL</code>, e.g.</p>
+
+<pre><code class="r">> length(x) <- 4
+> x
+A 'listenv' vector with 4 elements ('a', '', '', '').
+> x[[3]]
+NULL
+> x[[4]]
+NULL
+</code></pre>
+
+<p>To allocate an “empty” list environment (with all <code>NULL</code>:s) of a given length, do</p>
+
+<pre><code class="r">> x <- listenv()
+> length(x) <- 4
+> x
+A 'listenv' vector with 4 unnamed elements.
+</code></pre>
+
+<p><em>Note</em>: Unfortunately, it is <em>not</em> possible to use <code>x <- vector("listenv", length=4)</code>; that construct is only supported for the basic data types.</p>
+
+<h2>Iterating over elements</h2>
+
+<h3>Iterating over elements by names</h3>
+
+<p>Analogously to lists and plain environments, it is possible to iterate over elements of list environments by the element names. For example,</p>
+
+<pre><code class="r">> x <- listenv(a = 1, b = 2, c = 3)
+> for (name in names(x)) {
++ cat(sprintf("Element %s: %s\n", sQuote(name), x[[name]]))
++ }
+Element 'a': 1
+Element 'b': 2
+Element 'c': 3
+</code></pre>
+
+<h3>Iterating over elements by indices</h3>
+
+<p>Analogously to lists, but contrary to plain environments, it is also possible to iterate over elements by their indices. For example,</p>
+
+<pre><code class="r">> x <- listenv(a = 1, b = 2, c = 3)
+> for (ii in seq_along(x)) {
++ cat(sprintf("Element %d: %s\n", ii, x[[ii]]))
++ }
+Element 1: 1
+Element 2: 2
+Element 3: 3
+</code></pre>
+
+<h2>Coercion to and from list environments</h2>
+
+<h3>Coercing to lists and vectors</h3>
+
+<p>Coercing a list environment to a list:</p>
+
+<pre><code class="r">> x <- listenv(a = 2, b = 3, c = "hello")
+> x
+A 'listenv' vector with 3 elements ('a', 'b', 'c').
+> y <- as.list(x)
+> str(y)
+List of 3
+ $ a: num 2
+ $ b: num 3
+ $ c: chr "hello"
+</code></pre>
+
+<p>Coercing a list to a list environment:</p>
+
+<pre><code class="r">> z <- as.listenv(y)
+> z
+A 'listenv' vector with 3 elements ('a', 'b', 'c').
+> identical(z, x)
+[1] FALSE
+> all.equal(z, x)
+[1] TRUE
+</code></pre>
+
+<p>Unlisting:</p>
+
+<pre><code class="r">> unlist(x)
+ a b c
+ "2" "3" "hello"
+> unlist(x[-3])
+a b
+2 3
+> unlist(x[1:2], use.names = FALSE)
+[1] 2 3
+</code></pre>
+
+<h2>Multi-dimensional list environments</h2>
+
+<p>Analogously to lists, and contrary to plain environments, list environments can have dimensions with corresponding names. For example,</p>
+
+<pre><code class="r">> x <- as.listenv(1:6)
+> dim(x) <- c(2, 3)
+> dimnames(x) <- list(c("a", "b"), c("A", "B", "C"))
+> x
+A 'listenv' matrix with 6 unnamed elements arranged in 2x3 rows ('a', 'b') and columns ('A', 'B', 'C').
+</code></pre>
+
+<p>An easy way to quickly get an overview is to coerce to a list, e.g.</p>
+
+<pre><code class="r">> as.list(x)
+ A B C
+a 1 3 5
+b 2 4 6
+</code></pre>
+
+<p>Individual elements of a list environment can be accessed using standard subsetting syntax, e.g.</p>
+
+<pre><code class="r">> x[["a", "B"]]
+[1] 3
+> x[[1, 2]]
+[1] 3
+> x[[1, "B"]]
+[1] 3
+</code></pre>
+
+<p>We can assign individual elements similarly, e.g.</p>
+
+<pre><code class="r">> x[["b", "B"]] <- -x[["b", "B"]]
+> as.list(x)
+ A B C
+a 1 3 5
+b 2 -4 6
+</code></pre>
+
+<p>We can also assign multiple elements through dimensional subsetting, e.g.</p>
+
+<pre><code class="r">> x[2, -1] <- 98:99
+> as.list(x)
+ A B C
+a 1 3 5
+b 2 98 99
+> x["a", c(1, 3)] <- list(97, "foo")
+> as.list(x)
+ A B C
+a 97 3 "foo"
+b 2 98 99
+> x[] <- 1:6
+> as.list(x)
+ A B C
+a 1 3 5
+b 2 4 6
+</code></pre>
+
+<p>Concurrently with dimensional names it is possible to have names of the invidual elements just as for list environments without dimensions. For example,</p>
+
+<pre><code class="r">> names(x) <- letters[seq_along(x)]
+> x
+A 'listenv' matrix with 6 elements ('a', 'b', 'c', ..., 'f') arranged in 2x3 rows ('a', 'b') and columns ('A', 'B', 'C').
+> x[["a"]]
+[1] 1
+> x[["f"]]
+[1] 6
+> x[c("a", "f")]
+A 'listenv' vector with 2 elements ('a', 'f').
+> unlist(x)
+a b c d e f
+1 2 3 4 5 6
+</code></pre>
+
+<p>Contrary to lists, element names are preserved also with multi-dimensional subsetting, e.g.</p>
+
+<pre><code class="r">> x[1, 2]
+A 'listenv' vector with 1 element ('c').
+> x[1, 2, drop = FALSE]
+A 'listenv' matrix with 1 element ('c') arranged in 1x1 rows ('a') and columns ('B').
+> x[1:2, 2:1]
+A 'listenv' matrix with 4 elements ('c', 'd', 'a', 'b') arranged in 2x2 rows ('a', 'b') and columns ('B', 'A').
+> x[2, ]
+A 'listenv' vector with 3 elements ('b', 'd', 'f').
+> x[2, , drop = FALSE]
+A 'listenv' matrix with 3 elements ('b', 'd', 'f') arranged in 1x3 rows ('b') and columns ('A', 'B', 'C').
+> x["b", -2, drop = FALSE]
+A 'listenv' matrix with 2 elements ('b', 'f') arranged in 1x2 rows ('b') and columns ('A', 'C').
+</code></pre>
+
+<p>Note, whenever dimensions are set using <code>dim(x) <- dims</code> both the dimensional names and the element names are removed, e.g.</p>
+
+<pre><code class="r">> dim(x) <- NULL
+> names(x)
+NULL
+</code></pre>
+
+<p>This behavior is by design, cf. <code>help("dim", package="base")</code>.</p>
+
+<h3>Limitations</h3>
+
+<p>The current implementation does <em>not</em> support <em>dimensional subsetting</em> of more than one element. For instance, <code>x[1,]</code> is not supported by this version.</p>
+
+<h2>Important about environments</h2>
+
+<p>List environments are as their name suggests <em>environments</em>. Whenever working with environments in R, it is important to understand that <em>environments are mutable</em> whereas all other of the basic data types in R are immutable. For example, consider the following function that assigns zero to element <code>a</code> of object <code>x</code>:</p>
+
+<pre><code class="r">> setA <- function(x) {
++ x$a <- 0
++ x
++ }
+</code></pre>
+
+<p>If we pass a regular list to this function,</p>
+
+<pre><code class="r">> x <- list(a = 1)
+> y <- setA(x)
+> x$a
+[1] 1
+> y$a
+[1] 0
+</code></pre>
+
+<p>we see that <code>x</code> is unaffected by the assignment. This is because <em>lists are immutable</em> in R. However, if we pass an environment instead,</p>
+
+<pre><code class="r">> x <- new.env()
+> x$a <- 1
+> y <- setA(x)
+> x$a
+[1] 0
+> y$a
+[1] 0
+</code></pre>
+
+<p>we find that <code>x</code> was affected by the assignment. This is because <em>environments are mutable</em> in R. Since list environments inherits from environments, this also goes for them, e.g.</p>
+
+<pre><code class="r">> x <- listenv(a = 1)
+> y <- setA(x)
+> x$a
+[1] 0
+> y$a
+[1] 0
+</code></pre>
+
+<p>What is also important to understand is that it is not just the <em>content</em> of an environment that is mutable but also its <em>attributes</em>. For example,</p>
+
+<pre><code class="r">> x <- listenv(a = 1)
+> y <- x
+> attr(y, "foo") <- "Hello!"
+> attr(x, "foo")
+[1] "Hello!"
+</code></pre>
+
+<hr/>
+
+<p>Copyright Henrik Bengtsson, 2015</p>
+
+</body>
+
+</html>
diff --git a/inst/doc/listenv.md.rsp b/inst/doc/listenv.md.rsp
new file mode 100644
index 0000000..c5e1de6
--- /dev/null
+++ b/inst/doc/listenv.md.rsp
@@ -0,0 +1,360 @@
+<%@meta language="R-vignette" content="--------------------------------
+%\VignetteIndexEntry{List Environments}
+%\VignetteAuthor{Henrik Bengtsson}
+%\VignetteKeyword{R}
+%\VignetteKeyword{package}
+%\VignetteKeyword{vignette}
+%\VignetteKeyword{listenv}
+%\VignetteEngine{R.rsp::rsp}
+%\VignetteTangle{FALSE}
+--------------------------------------------------------------------"%>
+<%
+R.utils::use("R.utils")
+use("listenv")
+options("withCapture/newline"=FALSE)
+%>
+# <%@meta name="title"%>
+
+_List environments_ are environments that have list-like properties. They are implemented by the [listenv] package. The main features of a list environment are summarized in the below table:
+
+| Property | list environments | lists | environments |
+|------------------------------------------------------------------------|:-----------------:|:------:|:------------:|
+| Number of elements, e.g. `length()` | yes | yes | yes |
+| Named elements, e.g. `names()`, `x$a` and `x[["a"]]` | yes | yes | yes |
+| Duplicated names | yes | yes | |
+| Indexed elements, e.g. `x[[4]]` | yes | yes | |
+| Dimensions, e.g. `dim(x)` | yes | yes | |
+| Names of dimensions, e.g. `dimnames(x)` | yes | yes | |
+| Indexing by dimensions, e.g. `x[[2,4]]` and `x[[2,"D"]]` | yes | yes | |
+| Multi-element subsetting, e.g. `x[c("a", "c")]`, `x[-1]`, `[2:1,,3]` | yes | yes | |
+| Multi-element subsetting preserves element names | yes | | |
+| Removing element by assigning NULL, e.g. `x$c <- NULL` | yes | yes | |
+| Mutable, e.g. `y <- x; y$a <- 3; identical(y, x)` | yes | | yes |
+| Compatible* with `assign()`, `delayedAssign()`, `get()` and `exists()` | yes | | yes |
+
+For example,
+```r
+<%=withCapture({
+x <- listenv(a=1, b=2, c="hello")
+x
+
+length(x)
+names(x)
+x$a
+x[[3]] <- toupper(x[[3]])
+x$c
+
+y <- x
+y$d <- y$a + y[["b"]]
+names(y)[2] <- "a"
+y$a
+y
+identical(y, x)
+
+for (ii in seq_along(x)) {
+ cat(sprintf("Element %d (%s): %s\n", ii, sQuote(names(x)[ii]), x[[ii]]))
+}
+<%---
+get(map(x)["b"], envir=x)
+assign(map(x)["b"], 3, envir=x)
+---%>
+
+x[c(1,3)] <- list(2, "Hello world!")
+
+x
+y <- as.list(x)
+str(y)
+z <- as.listenv(y)
+z
+identical(z, x)
+all.equal(z, x)
+})%>
+```
+
+## Creating list environments
+List environments are created similarly to lists but also similarly to environments. To create an empty list environment, use
+```r
+<%=withCapture({
+x <- listenv()
+x
+})%>
+```
+This can later can be populated using named assignments,
+```r
+<%=withCapture({
+x$a <- 1
+x
+})%>
+```
+comparable to how both lists and environments work. Similarly to lists, they can also be populated using indices, e.g.
+```r
+<%=withCapture({
+x[[2]] <- 2
+x$c <- 3
+x
+})%>
+```
+Just as for lists, a list environment is expanded with `NULL` elements whenever a new element is added that is beyond the current length plus one, e.g.
+```r
+<%=withCapture({
+x[[5]] <- 5
+x
+x[[4]]
+})%>
+```
+
+As with lists, the above list environment can also be created from the start, e.g.
+```r
+<%=withCapture({
+x <- listenv(a=1, 3, c=4, NULL, 5)
+x
+})%>
+```
+
+
+As for lists, the length of a list environment can at any time be increased or decreased by assigning it a new length.
+If decreased, elements are dropped, e.g.
+```r
+<%=withCapture({
+x
+length(x) <- 2
+x
+x[[1]]
+x[[2]]
+})%>
+```
+If increased, new elements are populated with unnamed elements of `NULL`, e.g.
+```r
+<%=withCapture({
+length(x) <- 4
+x
+x[[3]]
+x[[4]]
+})%>
+```
+
+To allocate an "empty" list environment (with all `NULL`:s) of a given length, do
+```r
+<%=withCapture({
+x <- listenv()
+length(x) <- 4
+x
+})%>
+```
+_Note_: Unfortunately, it is _not_ possible to use `x <- vector("listenv", length=4)`; that construct is only supported for the basic data types.
+
+
+
+## Iterating over elements
+
+### Iterating over elements by names
+Analogously to lists and plain environments, it is possible to iterate over elements of list environments by the element names. For example,
+```r
+<%=withCapture({
+x <- listenv(a=1, b=2, c=3)
+for (name in names(x)) {
+ cat(sprintf("Element %s: %s\n", sQuote(name), x[[name]]))
+}
+})%>
+```
+
+### Iterating over elements by indices
+Analogously to lists, but contrary to plain environments, it is also possible to iterate over elements by their indices. For example,
+```r
+<%=withCapture({
+x <- listenv(a=1, b=2, c=3)
+for (ii in seq_along(x)) {
+ cat(sprintf("Element %d: %s\n", ii, x[[ii]]))
+}
+})%>
+```
+
+
+## Coercion to and from list environments
+
+### Coercing to lists and vectors
+
+Coercing a list environment to a list:
+```r
+<%=withCapture({
+x <- listenv(a=2, b=3, c="hello")
+x
+y <- as.list(x)
+str(y)
+})%>
+```
+
+Coercing a list to a list environment:
+```r
+<%=withCapture({
+z <- as.listenv(y)
+z
+identical(z, x)
+all.equal(z, x)
+})%>
+```
+
+Unlisting:
+```r
+<%=withCapture({
+unlist(x)
+unlist(x[-3])
+unlist(x[1:2], use.names=FALSE)
+})%>
+```
+
+
+## Multi-dimensional list environments
+
+Analogously to lists, and contrary to plain environments, list environments can have dimensions with corresponding names. For example,
+```r
+<%=withCapture({
+x <- as.listenv(1:6)
+dim(x) <- c(2,3)
+dimnames(x) <- list(c("a", "b"), c("A", "B","C"))
+x
+})%>
+```
+An easy way to quickly get an overview is to coerce to a list, e.g.
+```r
+<%=withCapture({
+as.list(x)
+})%>
+```
+Individual elements of a list environment can be accessed using standard subsetting syntax, e.g.
+```r
+<%=withCapture({
+x[["a", "B"]]
+x[[1, 2]]
+x[[1, "B"]]
+})%>
+```
+We can assign individual elements similarly, e.g.
+```r
+<%=withCapture({
+x[["b", "B"]] <- -x[["b", "B"]]
+as.list(x)
+})%>
+```
+We can also assign multiple elements through dimensional subsetting, e.g.
+```r
+<%=withCapture({
+x[2,-1] <- 98:99
+as.list(x)
+x["a",c(1,3)] <- list(97, "foo")
+as.list(x)
+x[] <- 1:6
+as.list(x)
+})%>
+```
+
+
+Concurrently with dimensional names it is possible to have names of the invidual elements just as for list environments without dimensions. For example,
+```r
+<%=withCapture({
+names(x) <- letters[seq_along(x)]
+x
+x[["a"]]
+x[["f"]]
+x[c("a", "f")]
+unlist(x)
+})%>
+```
+Contrary to lists, element names are preserved also with multi-dimensional subsetting, e.g.
+```r
+<%=withCapture({
+x[1,2]
+x[1,2,drop=FALSE]
+x[1:2,2:1]
+x[2,]
+x[2,,drop=FALSE]
+x["b",-2,drop=FALSE]
+})%>
+```
+
+
+Note, whenever dimensions are set using `dim(x) <- dims` both the dimensional names and the element names are removed, e.g.
+```r
+> dim(x) <- NULL
+> names(x)
+NULL
+```
+This behavior is by design, cf. `help("dim", package="base")`.
+<%---
+Because of this, the listenv package provides the `undim()` function, which removes the dimensions but preserves the names, e.g.
+```r
+<%=withCapture({
+x <- undim(x)
+names(x)
+})%>
+```
+_Warning_: Since list environments _and their attributes_ are mutable, calling
+```r
+undim(x)
+```
+will have the same effect as
+```r
+x <- undim(x)
+```
+That is, the dimension attributes of `x` will be changed. The reason for this is explained in Section 'Important about environments' above.
+---%>
+
+### Limitations
+The current implementation does _not_ support _dimensional subsetting_ of more than one element. For instance, `x[1,]` is not supported by this version.
+
+
+
+## Important about environments
+List environments are as their name suggests _environments_. Whenever working with environments in R, it is important to understand that _environments are mutable_ whereas all other of the basic data types in R are immutable. For example, consider the following function that assigns zero to element `a` of object `x`:
+```r
+<%=withCapture({
+setA <- function(x) {
+ x$a <- 0
+ x
+}
+})%>
+```
+If we pass a regular list to this function,
+```r
+<%=withCapture({
+x <- list(a=1)
+y <- setA(x)
+x$a
+y$a
+})%>
+```
+we see that `x` is unaffected by the assignment. This is because _lists are immutable_ in R. However, if we pass an environment instead,
+```r
+<%=withCapture({
+x <- new.env()
+x$a <- 1
+y <- setA(x)
+x$a
+y$a
+})%>
+```
+we find that `x` was affected by the assignment. This is because _environments are mutable_ in R. Since list environments inherits from environments, this also goes for them, e.g.
+```r
+<%=withCapture({
+x <- listenv(a=1)
+y <- setA(x)
+x$a
+y$a
+})%>
+```
+
+What is also important to understand is that it is not just the _content_ of an environment that is mutable but also its _attributes_. For example,
+```r
+<%=withCapture({
+x <- listenv(a=1)
+y <- x
+attr(y, "foo") <- "Hello!"
+attr(x, "foo")
+})%>
+```
+
+
+[listenv]: http://cran.r-project.org/package=listenv
+
+---
+Copyright Henrik Bengtsson, 2015
diff --git a/man/as.list.listenv.Rd b/man/as.list.listenv.Rd
new file mode 100644
index 0000000..c7aea62
--- /dev/null
+++ b/man/as.list.listenv.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/listenv.R
+\name{as.list.listenv}
+\alias{as.list.listenv}
+\title{List representation of a list environment}
+\usage{
+\method{as.list}{listenv}(x, all.names = TRUE, sorted = FALSE, ...)
+}
+\arguments{
+\item{x}{A list environment.}
+
+\item{all.names}{If \code{TRUE}, variable names starting with
+a period are included, otherwise not.}
+
+\item{sorted}{If \code{TRUE}, elements are ordered by their names
+before being compared, otherwise not.}
+
+\item{...}{Not used.}
+}
+\value{
+A list.
+}
+\description{
+List representation of a list environment
+}
+\keyword{internal}
+
diff --git a/man/cash-.listenv.Rd b/man/cash-.listenv.Rd
new file mode 100644
index 0000000..2d415a5
--- /dev/null
+++ b/man/cash-.listenv.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/listenv.R
+\name{$.listenv}
+\alias{$.listenv}
+\alias{[[.listenv}
+\title{Get elements of list environment}
+\usage{
+\method{$}{listenv}(x, name)
+}
+\arguments{
+\item{x}{A list environment.}
+
+\item{name}{The name or index of the element to retrieve.}
+}
+\value{
+The value of an element or NULL if the element does not exist
+}
+\description{
+Get elements of list environment
+}
+\keyword{internal}
+
diff --git a/man/cash-set-.listenv.Rd b/man/cash-set-.listenv.Rd
new file mode 100644
index 0000000..1b09ca3
--- /dev/null
+++ b/man/cash-set-.listenv.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/listenv.R
+\name{$<-.listenv}
+\alias{$<-.listenv}
+\alias{[[<-.listenv}
+\title{Set an element of list environment}
+\usage{
+\method{$}{listenv}(x, name) <- value
+}
+\arguments{
+\item{x}{A list environment.}
+
+\item{name}{Name or index of element}
+
+\item{value}{The value to assign to the element}
+}
+\description{
+Set an element of list environment
+}
+\keyword{internal}
+
diff --git a/man/get_variable.Rd b/man/get_variable.Rd
new file mode 100644
index 0000000..7679be4
--- /dev/null
+++ b/man/get_variable.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_variable.R
+\name{get_variable}
+\alias{get_variable}
+\alias{get_variable.listenv}
+\title{Get name of variable for a specific element of list environment}
+\usage{
+get_variable(...)
+}
+\arguments{
+\item{x}{A list environment.}
+
+\item{name}{The name or index of element of interest.}
+
+\item{mustExist}{If TRUE, an error is generated if \code{name}
+does not exist.}
+
+\item{create}{If TRUE, element \code{name} is created if missing.}
+}
+\value{
+The name of the underlying variable
+}
+\description{
+Get name of variable for a specific element of list environment
+}
+\keyword{internal}
+
diff --git a/man/length.listenv.Rd b/man/length.listenv.Rd
new file mode 100644
index 0000000..243bb5d
--- /dev/null
+++ b/man/length.listenv.Rd
@@ -0,0 +1,16 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/listenv.R
+\name{length.listenv}
+\alias{length.listenv}
+\title{Number of elements in list environment}
+\usage{
+\method{length}{listenv}(x)
+}
+\arguments{
+\item{x}{A list environment.}
+}
+\description{
+Number of elements in list environment
+}
+\keyword{internal}
+
diff --git a/man/listenv.Rd b/man/listenv.Rd
new file mode 100644
index 0000000..8dbd844
--- /dev/null
+++ b/man/listenv.Rd
@@ -0,0 +1,33 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/listenv.R
+\name{listenv}
+\alias{as.listenv}
+\alias{listenv}
+\title{Create a list environment}
+\usage{
+listenv(...)
+
+as.listenv(...)
+}
+\arguments{
+\item{\dots}{(optional) Named and/or unnamed objects to be
+assigned to the list environment.}
+}
+\value{
+An environment of class `listenv`.
+}
+\description{
+Create a list environment
+}
+\examples{
+x <- listenv(c=2, a=3, d="hello")
+print(names(x))
+names(x)[2] <- "A"
+x$b <- 5:8
+
+y <- as.list(x)
+str(y)
+
+z <- as.listenv(y)
+}
+
diff --git a/man/map.Rd b/man/map.Rd
new file mode 100644
index 0000000..cc6d24a
--- /dev/null
+++ b/man/map.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/listenv.R
+\name{map}
+\alias{map}
+\alias{map.listenv}
+\title{Variable name map for elements of list environment}
+\usage{
+map(x, ...)
+}
+\arguments{
+\item{x}{A list environment.}
+}
+\value{
+The a named character vector
+}
+\description{
+Variable name map for elements of list environment
+}
+\keyword{internal}
+
diff --git a/man/names.listenv.Rd b/man/names.listenv.Rd
new file mode 100644
index 0000000..72620f6
--- /dev/null
+++ b/man/names.listenv.Rd
@@ -0,0 +1,17 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/listenv.R
+\name{names.listenv}
+\alias{names.listenv}
+\alias{names<-.listenv}
+\title{Names of elements in list environment}
+\usage{
+\method{names}{listenv}(x)
+}
+\arguments{
+\item{x}{A list environment.}
+}
+\description{
+Names of elements in list environment
+}
+\keyword{internal}
+
diff --git a/man/parse_env_subset.Rd b/man/parse_env_subset.Rd
new file mode 100644
index 0000000..43d2890
--- /dev/null
+++ b/man/parse_env_subset.Rd
@@ -0,0 +1,24 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/parse_env_subset.R
+\name{parse_env_subset}
+\alias{parse_env_subset}
+\title{Helper function to infer target from expression and environment}
+\usage{
+parse_env_subset(expr, envir = parent.frame(), substitute = TRUE)
+}
+\arguments{
+\item{expr}{An expression.}
+
+\item{envir}{An environment.}
+
+\item{substitute}{If TRUE, then the expression is
+\code{substitute()}:ed, otherwise not.}
+}
+\value{
+A named list.
+}
+\description{
+Helper function to infer target from expression and environment
+}
+\keyword{internal}
+
diff --git a/man/undim.Rd b/man/undim.Rd
new file mode 100644
index 0000000..33ab05e
--- /dev/null
+++ b/man/undim.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/undim.R
+\name{undim}
+\alias{undim}
+\alias{undim.default}
+\title{Removes the dimension of an object}
+\usage{
+undim(x, ...)
+}
+\arguments{
+\item{x}{An object with or without dimensions}
+
+\item{...}{Not used.}
+}
+\value{
+The object with the dimension attribute removed.
+}
+\description{
+Removes the dimension of an object
+}
+\details{
+This function does \code{attr(x, "dim") <- NULL}, which
+automatically also does \code{attr(x, "dimnames") <- NULL}.
+However, other attributes such as names attributes are preserved,
+which is not the case if one do \code{dim(x) <- NULL}.
+}
+
diff --git a/tests/as.listenv.R b/tests/as.listenv.R
new file mode 100644
index 0000000..2bc9b92
--- /dev/null
+++ b/tests/as.listenv.R
@@ -0,0 +1,33 @@
+library("listenv")
+
+ovars <- ls(envir=globalenv())
+oopts <- options(warn=1)
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Single-element assignments and subsetting
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- list(a=1, b=2, c=3)
+str(x)
+y <- as.listenv(x)
+print(y)
+stopifnot(identical(as.list(y), x))
+z <- as.listenv(y)
+stopifnot(identical(as.list(y), as.list(z)))
+
+e <- new.env()
+e$a <- 1
+e$b <- 2
+e$c <- 3
+y <- as.listenv(e)
+print(y)
+stopifnot(identical(as.list(y), as.list(e)))
+
+x <- c(a=1, b=2, c=3)
+y <- as.listenv(x)
+print(y)
+stopifnot(identical(as.list(y), as.list(x)))
+
+## Cleanup
+options(oopts)
+rm(list=setdiff(ls(envir=globalenv()), ovars), envir=globalenv())
diff --git a/tests/get_variable,dimensions.R b/tests/get_variable,dimensions.R
new file mode 100644
index 0000000..a08f603
--- /dev/null
+++ b/tests/get_variable,dimensions.R
@@ -0,0 +1,38 @@
+library("listenv")
+
+ovars <- ls(envir=globalenv())
+oopts <- options(warn=1)
+map <- listenv:::map
+
+message("* get_variable() - multi-dimensional list environments ...")
+
+x <- listenv()
+length(x) <- 6
+dim(x) <- c(2,3)
+
+for (ii in seq_along(x)) {
+ stopifnot(is.null(x[[ii]]))
+ idx <- arrayInd(ii, .dim=dim(x))
+ stopifnot(is.null(x[[idx[1],idx[2]]]))
+ varV <- get_variable(x, ii, create=FALSE)
+ varA <- get_variable(x, idx, create=FALSE)
+ stopifnot(identical(varA, varV))
+}
+
+x[1:6] <- 1:6
+for (ii in seq_along(x)) {
+ stopifnot(identical(x[[ii]], ii))
+ idx <- arrayInd(ii, .dim=dim(x))
+ stopifnot(identical(x[[idx[1],idx[2]]], ii))
+
+ varV <- get_variable(x, ii)
+ varA <- get_variable(x, idx)
+ stopifnot(identical(varA, varV))
+}
+
+
+message("* get_variable() - multi-dimensional list environments ... DONE")
+
+## Cleanup
+options(oopts)
+rm(list=setdiff(ls(envir=globalenv()), ovars), envir=globalenv())
diff --git a/tests/get_variable.R b/tests/get_variable.R
new file mode 100644
index 0000000..625a331
--- /dev/null
+++ b/tests/get_variable.R
@@ -0,0 +1,100 @@
+library("listenv")
+
+ovars <- ls(envir=globalenv())
+oopts <- options(warn=1)
+map <- listenv:::map
+
+x <- listenv()
+length(x) <- 3L
+names(x) <- c("a", "b", "c")
+stopifnot(length(x) == 3L)
+print(map(x))
+
+var <- get_variable(x, "a")
+stopifnot(!is.na(var))
+stopifnot(length(x) == 3L)
+print(map(x))
+
+var <- get_variable(x, "b")
+stopifnot(!is.na(var))
+stopifnot(length(x) == 3L)
+print(map(x))
+
+var <- get_variable(x, "c")
+stopifnot(!is.na(var))
+stopifnot(length(x) == 3L)
+print(map(x))
+
+var <- get_variable(x, "d")
+stopifnot(!is.na(var))
+stopifnot(length(x) == 4L)
+print(map(x))
+
+var <- get_variable(x, 4L)
+stopifnot(!is.na(var))
+stopifnot(length(x) == 4L)
+print(map(x))
+
+x$b <- 2
+var <- get_variable(x, "b")
+stopifnot(!is.na(var))
+stopifnot(length(x) == 4L)
+print(map(x))
+
+var <- get_variable(x, length(x) + 1L)
+stopifnot(length(x) == 5L)
+print(names(x))
+print(map(x))
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Allocation
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+length(x) <- 3L
+print(x[[1]])
+print(x[[2]])
+print(x[[3]])
+
+## Out-of-bound subsetting
+res <- try(x[[0]], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+## Out-of-bound subsetting
+res <- try(x[[4]], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+print(get_variable(x, 1L, mustExist=FALSE))
+print(get_variable(x, 2L, mustExist=FALSE))
+print(get_variable(x, 3L, mustExist=FALSE))
+
+## Out-of-bound element
+res <- try(var <- get_variable(x, 0L, mustExist=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+## Out-of-bound element
+res <- try(var <- get_variable(x, length(x) + 1L, mustExist=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Exception handling
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+length(x) <- 3L
+names(x) <- c("a", "b", "c")
+
+## Non-existing element
+res <- try(var <- get_variable(x, "z", mustExist=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(var <- get_variable(x, c("a", "b")), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(var <- get_variable(x, 1+2i), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+
+
+## Cleanup
+options(oopts)
+rm(list=setdiff(ls(envir=globalenv()), ovars), envir=globalenv())
diff --git a/tests/listenv,dimensions.R b/tests/listenv,dimensions.R
new file mode 100644
index 0000000..4c7fecb
--- /dev/null
+++ b/tests/listenv,dimensions.R
@@ -0,0 +1,303 @@
+library("listenv")
+
+ovars <- ls(envir=globalenv())
+oopts <- options(warn=1)
+
+
+message("* List environment and multiple dimensions ...")
+
+x <- listenv()
+dim(x) <- c(0,0)
+print(x)
+stopifnot(length(x) == 0)
+
+x <- listenv(a=1)
+stopifnot(identical(names(x), "a"))
+dim(x) <- c(1,1)
+print(x)
+stopifnot(length(x) == 1)
+stopifnot(is.null(dimnames(x)))
+stopifnot(is.null(names(x)))
+
+x0 <- as.list(1:6)
+x <- as.listenv(x0)
+print(x)
+stopifnot(is.null(dim(x)))
+stopifnot(is.null(dimnames(x)))
+y <- as.list(x)
+stopifnot(identical(y, x0))
+z <- as.listenv(y)
+stopifnot(all.equal(z, x))
+
+
+message("* dim(x) and dimnames(x) ...")
+dims <- list(2:3, 2:4)
+for (kk in seq_along(dims)) {
+ dim <- dims[[kk]]
+ dimnames <- lapply(dim, FUN=function(n) letters[seq_len(n)])
+ names <- letters[seq_len(prod(dim))]
+ str(list(dim=dim, dimnames=dimnames, names=names))
+
+ n <- prod(dim)
+ values <- seq_len(n)
+
+ x0 <- as.list(values)
+ x <- as.listenv(values)
+ print(x)
+ stopifnot(identical(dim(x), dim(x0)))
+ y <- as.list(x)
+ stopifnot(identical(y, x0))
+ z <- as.listenv(y)
+ stopifnot(all.equal(z, x))
+
+ dim(x0) <- dim
+ dim(x) <- dim
+ print(x)
+ stopifnot(identical(dim(x), dim(x0)))
+ stopifnot(is.null(dimnames(x)))
+ stopifnot(is.null(names(x)))
+ names(x0) <- names
+ names(x) <- names
+ y <- as.list(x)
+ stopifnot(identical(y, x0))
+ z <- as.listenv(y)
+ stopifnot(all.equal(z, x))
+
+ excls <- c(list(NULL), as.list(seq_along(dimnames)), list(seq_along(dimnames)))
+ for (ll in seq_along(excls)) {
+ excl <- excls[[ll]]
+ dimnamesT <- dimnames
+ dimnamesT[excl] <- list(NULL)
+ dimnames(x0) <- dimnamesT
+ dimnames(x) <- dimnamesT
+ print(x)
+ stopifnot(identical(dim(x), dim(x0)))
+ stopifnot(identical(dimnames(x), dimnames(x0)))
+ stopifnot(identical(names(x), names))
+ y <- as.list(x)
+ stopifnot(identical(y, x0))
+ z <- as.listenv(y)
+ stopifnot(all.equal(z, x))
+ } ## for (ll ...)
+} ## for (kk ...)
+
+
+# Assign names
+x <- as.listenv(1:6)
+dim(x) <- c(2,3)
+dimnames(x) <- lapply(dim(x), FUN=function(n) letters[seq_len(n)])
+names(x) <- letters[seq_along(x)]
+print(x)
+stopifnot(!is.null(dim(x)))
+stopifnot(!is.null(dimnames(x)))
+stopifnot(!is.null(names(x)))
+stopifnot(x[["b"]] == 2L)
+stopifnot(x[["a", "b"]] == 3L)
+
+## Extract single element
+message("* y <- x[[i,j]] and z <- x[i,j] ...")
+dim(x) <- c(2,3)
+dimnames(x) <- list(c("a", "b"), NULL)
+
+y <- x[[3]]
+stopifnot(identical(y, 3L))
+z <- x[3]
+stopifnot(identical(z[[1]], y))
+
+y <- x[[1,1]]
+stopifnot(identical(y, x[[1]]))
+z <- x[1,1]
+stopifnot(identical(z[[1]], y))
+
+y <- x[[2,3]]
+stopifnot(identical(y, x[[6]]))
+z <- x[2,3]
+stopifnot(identical(z[[1]], y))
+
+y <- x[["a",3]]
+stopifnot(identical(y, x[[1,3]]))
+stopifnot(identical(y, x[[5]]))
+z <- x["a",3]
+stopifnot(identical(z[[1]], y))
+
+
+y <- x[[1,c(FALSE,FALSE,TRUE)]]
+stopifnot(identical(y, x[[1,3]]))
+stopifnot(identical(y, x[[5]]))
+z <- x[1,c(FALSE,FALSE,TRUE)]
+stopifnot(identical(z[[1]], y))
+
+
+message("* x[[i,j]] <- value ...")
+## Assign single element
+x[[3]] <- -x[[3]]
+stopifnot(identical(x[[3]], -3L))
+
+x[[1,1]] <- -x[[1,1]]
+stopifnot(identical(x[[1]], -1L))
+
+x[[2,3]] <- -x[[2,3]]
+stopifnot(identical(x[[6]], -6L))
+
+x[["a",3]] <- -x[["a",3]]
+stopifnot(identical(x[[1,3]], -5L))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Multi-element subsetting
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+message("* x[i], x[i,j] ...")
+x <- as.listenv(1:24)
+dim(x) <- c(2,3,4)
+names(x) <- letters[seq_along(x)]
+x[2] <- list(NULL)
+print(x)
+
+y <- x[]
+print(y)
+stopifnot(length(y) == length(x))
+stopifnot(all.equal(y, x))
+stopifnot(!identical(y, x))
+stopifnot(all.equal(as.list(y), as.list(x)[]))
+
+y <- x[1]
+print(y)
+stopifnot(all.equal(as.list(y), as.list(x)[1]))
+
+y <- x[2:3]
+print(y)
+stopifnot(all.equal(as.list(y), as.list(x)[2:3]))
+
+y <- x[-1]
+print(y)
+stopifnot(all.equal(as.list(y), as.list(x)[-1]))
+
+y <- x[1:2,1:3,1:4]
+print(y)
+stopifnot(all.equal(dim(y), dim(x)))
+stopifnot(all.equal(y, x))
+stopifnot(all.equal(unlist(y), unlist(x)))
+stopifnot(all.equal(as.list(y), as.list(x)[1:2,1:3,1:4], check.attributes=FALSE))
+
+y <- x[0,0,0]
+print(y)
+stopifnot(length(y) == 0)
+stopifnot(all.equal(dim(y), c(0,0,0)))
+stopifnot(all.equal(y, as.list(x)[0,0,0]))
+
+y <- x[0,,]
+print(y)
+stopifnot(length(y) == 0)
+stopifnot(all.equal(dim(y), c(0,dim(x)[-1])))
+stopifnot(all.equal(y, as.list(x)[0,,]))
+
+y <- x[2,1,,drop=FALSE]
+print(y)
+stopifnot(all.equal(dim(y), c(1,1,dim(x)[3])))
+stopifnot(all.equal(as.list(y), as.list(x)[2,1,,drop=FALSE], check.attributes=FALSE))
+
+y <- x[2,1,,drop=TRUE]
+print(y)
+stopifnot(is.null(dim(y)))
+stopifnot(all.equal(as.list(y), as.list(x)[2,1,,drop=TRUE], check.attributes=FALSE))
+
+y <- x[2,1,]
+print(y)
+stopifnot(is.null(dim(y)))
+stopifnot(all.equal(as.list(y), as.list(x)[2,1,], check.attributes=FALSE))
+
+y <- x[-1,,c(3,3,1)]
+print(y)
+stopifnot(all.equal(as.list(y), as.list(x)[-1,,c(3,3,1)], check.attributes=FALSE))
+
+message("* x[i], x[i,j] ... DONE")
+
+
+message("* x[i] <- value, x[i,j] <- value ...")
+dim <- c(2,3)
+n <- prod(dim)
+names <- letters[seq_len(n)]
+
+x0 <- as.list(1:n)
+dim(x0) <- dim
+names(x0) <- names
+
+x <- as.listenv(1:n)
+dim(x) <- dim
+names(x) <- names
+
+x0[] <- 6:1
+x[] <- 6:1
+stopifnot(all(unlist(x) == unlist(x0)))
+
+x0[1,] <- 1:3
+x[1,] <- 1:3
+stopifnot(all(unlist(x) == unlist(x0)))
+
+x0[,-2] <- 1:2
+x[,-2] <- 1:2
+stopifnot(all(unlist(x) == unlist(x0)))
+
+message("* x[i] <- value, x[i,j] <- value ... DONE")
+
+
+message("* Exceptions ...")
+x <- listenv()
+res <- try(dim(x) <- c(2,3), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+length(x) <- 6
+dim(x) <- c(2,3)
+
+res <- try(x[[3,3]], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[3,3], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[c(-1,1),3], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[c(TRUE, TRUE, TRUE),], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(dimnames(x) <- NA, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(dimnames(x) <- list("a", "b", "c"), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(dimnames(x) <- list("a", NULL), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+dimnames(x) <- list(c("a", "b"), NULL)
+
+
+message("* Changing dim(x) and dimnames(x) ...")
+x <- listenv()
+x[1:12] <- 1:12
+dim(x) <- c(2,2,3)
+dimnames(x) <- list(c("a", "b"), NULL, NULL)
+print(x)
+stopifnot(identical(dim(x), c(2L,2L,3L)))
+stopifnot(identical(dimnames(x), list(c("a", "b"), NULL, NULL)))
+x[[2,1,2]] <- -x[[2,1,2]]
+y <- unlist(x)
+print(y)
+
+dim(x) <- c(4,3)
+print(x)
+stopifnot(identical(dim(x), c(4L,3L)))
+stopifnot(is.null(dimnames(x)))
+x[[2,2]] <- -x[[2,2]]
+y <- unlist(x)
+print(y)
+stopifnot(identical(y, 1:12))
+
+
+message("* List environment and multiple dimensions ... DONE")
+
+
+## Cleanup
+options(oopts)
+rm(list=setdiff(ls(envir=globalenv()), ovars), envir=globalenv())
diff --git a/tests/listenv.R b/tests/listenv.R
new file mode 100644
index 0000000..eeef4e0
--- /dev/null
+++ b/tests/listenv.R
@@ -0,0 +1,692 @@
+library("listenv")
+
+ovars <- ls(envir=globalenv())
+oopts <- options(warn=1)
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Allocation
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+print(x)
+stopifnot(length(x) == 0)
+stopifnot(is.null(names(x)))
+
+x <- listenv(a=1)
+print(x)
+stopifnot(length(x) == 1)
+stopifnot(identical(names(x), c("a")))
+stopifnot(identical(x$a, 1))
+
+x <- listenv(a=1, b=2:3)
+print(x)
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+stopifnot(identical(x$a, 1), identical(x$b, 2:3))
+
+
+x <- listenv(b=2:3, .a=1)
+print(x)
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("b", ".a")))
+stopifnot(identical(x$.a, 1), identical(x$b, 2:3))
+
+
+x <- listenv(length=3, a=1)
+print(x)
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("length", "a")))
+stopifnot(identical(x$length, 3), identical(x$a, 1))
+
+
+withCallingHandlers({
+ x <- listenv(length=3)
+}, warning = function(warn) {
+ cat("WARNING:", warn$message)
+ invokeRestart("muffleWarning")
+})
+print(x)
+stopifnot(length(x) == 3)
+stopifnot(is.null(names(x)))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Single-element assignments and subsetting
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 0)
+
+x$a <- 1
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 1)
+stopifnot(identical(names(x), c("a")))
+stopifnot(identical(x$a, 1), is.null(x$b))
+
+x$b <- 2
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+stopifnot(identical(x$b, 2))
+
+x$a <- 0
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+stopifnot(identical(x[["a"]], 0))
+
+x$"a" <- 1
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+stopifnot(identical(x$a, 1))
+
+x[["a"]] <- 0
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+
+
+key <- "b"
+x[[key]] <- 3
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+stopifnot(identical(x$b, 3), identical(x[["b"]], 3), identical(x[[key]], 3))
+
+x[[3]] <- 3.14
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("a", "b", "")))
+stopifnot(identical(x[[3]], 3.14))
+
+names(x) <- c("a", "b", "c")
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("a", "b", "c")))
+stopifnot(identical(x[[3]], 3.14), identical(x[["c"]], 3.14), identical(x$c, 3.14))
+
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Multi-element subsetting
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Assert than no false names are introduced
+x <- listenv()
+x[1:3] <- list(1, NULL, 3)
+print(x)
+stopifnot(is.null(names(x)))
+
+y <- x[]
+print(y)
+stopifnot(length(y) == length(x))
+stopifnot(all.equal(y, x))
+stopifnot(!identical(y, x))
+stopifnot(is.null(names(y)))
+
+y <- x[1]
+print(y)
+stopifnot(is.null(names(y)))
+
+y <- x[2:3]
+print(y)
+stopifnot(is.null(names(y)))
+
+y <- x[-1]
+print(y)
+stopifnot(is.null(names(y)))
+
+x[c('c', '.a', 'b')] <- list(NULL, 3, 1)
+print(x)
+stopifnot(identical(names(x), c("", "", "", "c", ".a", "b")))
+
+y <- as.list(x)
+str(y)
+stopifnot(identical(names(y), c("", "", "", "c", ".a", "b")))
+
+y <- as.list(x, all.names=FALSE)
+str(y)
+stopifnot(identical(names(y), c("", "", "", "c", "b")))
+
+y <- as.list(x, sorted=TRUE)
+str(y)
+stopifnot(identical(names(y), c("", "", "", ".a", "b", "c")))
+
+y <- as.list(x, all.names=FALSE, sorted=TRUE)
+str(y)
+stopifnot(identical(names(y), c("", "", "", "b", "c")))
+
+
+x <- listenv()
+x[c('a', 'b', 'c')] <- list(1, NULL, 3)
+
+y <- x[NULL]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list()))
+
+y <- x[integer(0L)]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list()))
+
+y <- x["a"]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(a=1)))
+
+y <- x[c("a","c")]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(a=1, c=3)))
+
+y <- x[c("c","a")]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(c=3, a=1)))
+
+y <- x[c(1,3)]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(a=1, c=3)))
+
+y <- x[-2]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(a=1, c=3)))
+
+y <- x[-c(1,3)]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(b=NULL)))
+
+y <- x[rep(1L, times=6L)]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, rep(list(a=1), times=6L)))
+
+y <- x[1:10]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, c(as.list(x), rep(list(NULL), times=7L))))
+
+
+y <- x[c(TRUE, FALSE, TRUE)]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(a=1, c=3)))
+
+y <- x[c(TRUE, FALSE)]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(a=1, c=3)))
+
+y <- x[TRUE]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, as.list(x)))
+
+y <- x[FALSE]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list()))
+
+y <- x[rep(TRUE, times=5L)]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, c(as.list(x), list(NULL), list(NULL))))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Local access
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv(a=1, b=2, c=3.14)
+
+y <- local({
+ x[[3]]
+})
+stopifnot(identical(y, 3.14))
+
+y <- local({
+ x[3]
+})
+stopifnot(identical(y[[1]], 3.14))
+
+y <- local({
+ ii <- 3
+ x[[ii]]
+})
+stopifnot(identical(y, 3.14))
+
+y <- local({
+ ii <- 3
+ x[ii]
+})
+stopifnot(identical(y[[1]], 3.14))
+
+
+local({
+ x[[3]] <- 42L
+})
+y <- x[[3]]
+stopifnot(identical(y, 42L))
+
+local({
+ x[3] <- 3.14
+})
+y <- x[[3]]
+stopifnot(identical(y, 3.14))
+
+local({
+ ii <- 3
+ x[ii] <- 42L
+})
+y <- x[[3]]
+stopifnot(identical(y, 42L))
+
+local({
+ ii <- 3
+ x[[ii]] <- 3.14
+})
+y <- x[[3]]
+stopifnot(identical(y, 3.14))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Removing elements
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x[["a"]] <- NULL
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("b", "c")))
+
+x[[3L]] <- NULL
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("b", "c")))
+
+x[[2L]] <- NULL
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 1)
+stopifnot(identical(names(x), c("b")))
+
+x$b <- NULL
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 0)
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Assigning NULL
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x[2L] <- list(NULL)
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("", "")))
+
+x['c'] <- list(NULL)
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("", "", "c")))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Assigning multiple elements at once
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+x[c('a', 'b', 'c')] <- 1:3
+print(x)
+str(as.list(x))
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("a", "b", "c")))
+stopifnot(identical(as.list(x), list(a=1L, b=2L, c=3L)))
+stopifnot(identical(unlist(x), c(a=1L, b=2L, c=3L)))
+
+x[] <- 3:1
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("a", "b", "c")))
+stopifnot(identical(as.list(x), list(a=3L, b=2L, c=1L)))
+
+x[c('c', 'b')] <- 2:3
+print(x)
+str(as.list(x))
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("a", "b", "c")))
+stopifnot(identical(as.list(x), list(a=3L, b=3L, c=2L)))
+
+x[c('a', 'c')] <- 1L
+print(x)
+str(as.list(x))
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("a", "b", "c")))
+stopifnot(identical(as.list(x), list(a=1L, b=3L, c=1L)))
+
+x[c('d', 'e')] <- 4:5
+print(x)
+str(as.list(x))
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 5)
+stopifnot(identical(names(x), c("a", "b", "c", "d", "e")))
+stopifnot(identical(as.list(x), list(a=1L, b=3L, c=1L, d=4L, e=5L)))
+
+
+x <- listenv()
+x[c('a', 'b')] <- 1:2
+x[c(TRUE,FALSE)] <- 2L
+print(x)
+str(as.list(x))
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+stopifnot(identical(as.list(x), list(a=2L, b=2L)))
+
+x[c(TRUE)] <- 1L
+print(x)
+str(as.list(x))
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+stopifnot(identical(as.list(x), list(a=1L, b=1L)))
+
+x[c(TRUE,FALSE,TRUE,FALSE)] <- 3L
+print(x)
+str(as.list(x))
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("a", "b", "")))
+stopifnot(identical(as.list(x), list(a=3L, b=1L, 3L)))
+
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Expanding
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+for (ii in 1:3) {
+ x[[ii]] <- letters[ii]
+ print(x[[ii]])
+}
+print(x)
+names(x) <- sprintf("item%d", seq_along(x))
+print(x)
+
+y <- as.list(x)
+str(y)
+stopifnot(identical(names(y), c("item1", "item2", "item3")))
+stopifnot(identical(y[[1]], "a"), identical(y[[2]], "b"), identical(y[[3]], "c"))
+x[[2]] <- "B"
+stopifnot(identical(x$item2, "B"))
+
+
+x <- listenv()
+x[[1]] <- { 1 }
+x[[3]] <- { "Hello world!" }
+stopifnot(length(x) == 3)
+stopifnot(identical(seq_along(x), seq_len(length(x))))
+print(x)
+names(x) <- c("a", "b", "c")
+print(x)
+x$b <- TRUE
+stopifnot(identical(x[[1]], 1))
+stopifnot(identical(x[[2]], TRUE))
+stopifnot(identical(x$b, TRUE))
+stopifnot(identical(x[["b"]], TRUE))
+y <- as.list(x)
+str(y)
+stopifnot(length(y) == 3)
+
+
+## Mixed names and indices
+x <- listenv()
+x$a <- 1
+x[[3]] <- 3
+print(names(x))
+stopifnot(identical(names(x), c("a", "", "")))
+
+# First element (should be named 'a')
+var <- get_variable(x, "a")
+stopifnot(var == "a")
+var <- get_variable(x, 1)
+stopifnot(var == "a")
+
+# Third element (should be a temporary name)
+var <- get_variable(x, 3)
+stopifnot(var != "c")
+names(x) <- c("a", "b", "c")
+var <- get_variable(x, 3)
+stopifnot(var != "c")
+var <- get_variable(x, "c")
+stopifnot(var != "c")
+
+## Second element (should become 'b', because it was never used
+# before it was "named" 'b')
+x$b <- 2
+var <- get_variable(x, 2)
+stopifnot(var == "b")
+var <- get_variable(x, "b")
+stopifnot(var == "b")
+
+
+## Names where as.integer(names(x)) are integers
+x <- listenv()
+x[["1"]] <- 1
+x[["3"]] <- 3
+print(names(x))
+stopifnot(identical(names(x), c("1", "3")))
+
+
+## Expand and shrink
+x <- listenv()
+stopifnot(length(x) == 0L)
+length(x) <- 3L
+stopifnot(length(x) == 3L)
+stopifnot(is.null(names(x)))
+
+names(x) <- c("a", "b", "c")
+x$a <- 2
+stopifnot(identical(x$a, 2))
+x[c("a", "c")] <- c(2,1)
+stopifnot(identical(x$a, 2), identical(x$c, 1))
+
+length(x) <- 4L
+stopifnot(length(x) == 4L)
+stopifnot(identical(names(x), c("a", "b", "c", "")))
+
+length(x) <- 1L
+stopifnot(length(x) == 1L)
+stopifnot(identical(names(x), c("a")))
+stopifnot(identical(x$a, 2))
+
+length(x) <- 0L
+stopifnot(length(x) == 0L)
+stopifnot(length(names(x)) == 0) ## Actually, character(0), cf. lists
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Flatten
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+for (recursive in c(FALSE, TRUE)) {
+ x <- list(); x$a <- list(B=1:3); x$b <- list(C=1:3, D=4:5)
+ y1 <- unlist(x, recursive=recursive)
+
+ x <- listenv(); x$a <- list(B=1:3); x$b <- list(C=1:3, D=4:5)
+ y2 <- unlist(x, recursive=recursive)
+ stopifnot(identical(y2, y1))
+} # for (recursive ...)
+
+x <- listenv(); x$a <- list(B=1:3); x$b <- as.listenv(list(C=1:3, D=4:5))
+y3 <- unlist(x, recursive=TRUE)
+stopifnot(identical(y3, y1))
+
+x <- listenv()
+y <- unlist(x)
+stopifnot(length(y) == 0)
+stopifnot(is.null(y))
+
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Comparisons
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv(c=NULL, .a=3, b=1)
+print(x)
+
+## A list environment is always equal to itself
+stopifnot(all.equal(x, x))
+
+## List environments emulate lists
+stopifnot(all.equal(x, list(c=NULL, .a=3, b=1)))
+stopifnot(all.equal(x, list(c=NULL, .a=3, b=1), sorted=TRUE))
+stopifnot(all.equal(x, list(.a=3, b=1, c=NULL), sorted=TRUE))
+
+stopifnot(all.equal(x, list(c=NULL, b=1), all.names=FALSE))
+stopifnot(all.equal(x, list(.a=3, c=NULL, b=1), all.names=FALSE))
+stopifnot(all.equal(x, list(b=1, c=NULL), all.names=FALSE, sorted=TRUE))
+
+res <- all.equal(x, list(b=1, c=NULL), sorted=FALSE)
+stopifnot(!isTRUE(res))
+
+res <- all.equal(x, list(b=1, c=NULL), all.names=FALSE)
+stopifnot(!isTRUE(res))
+
+## Assert listenv() -> as.list() -> as.listenv() equality
+y <- as.list(x)
+stopifnot(identical(names(y), names(x)))
+z <- as.listenv(y)
+stopifnot(identical(names(z), names(y)))
+stopifnot(all.equal(x, y))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Warnings
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+x[1:3] <- 1:3
+res <- tryCatch(x[1:2] <- 1:4, warning=function(w) {
+ class(w) <- "try-warning"
+ w
+})
+stopifnot(inherits(res, "try-warning"))
+
+res <- tryCatch(x[1:3] <- 1:2, warning=function(w) {
+ class(w) <- "try-warning"
+ w
+})
+stopifnot(inherits(res, "try-warning"))
+
+res <- tryCatch(x[integer(0L)] <- 1, warning=function(w) {
+ class(w) <- "try-warning"
+ w
+})
+stopifnot(!inherits(res, "try-warning"))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Exception handling
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+length(x) <- 3L
+names(x) <- c("a", "b", "c")
+
+res <- try(names(x) <- c("a", "b"), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[1:2]], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[0]], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[length(x)+1]], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[1+2i]], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[1+2i], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[1+2i]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[1+2i] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[integer(0L)]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[1:2]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[Inf]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[0]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[-1]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[character(0L)]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[c("a", "b")]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[""]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+
+## Cleanup
+options(oopts)
+rm(list=setdiff(ls(envir=globalenv()), ovars), envir=globalenv())
diff --git a/tests/parse_env_subset,dimensions.R b/tests/parse_env_subset,dimensions.R
new file mode 100644
index 0000000..e2783e6
--- /dev/null
+++ b/tests/parse_env_subset,dimensions.R
@@ -0,0 +1,105 @@
+library("listenv")
+
+ovars <- ls(envir=globalenv())
+if (exists("x")) rm(list="x")
+if (exists("y")) rm(list="y")
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Multi-dimensional subsetting
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+message("*** parse_env_subset() on multi-dimensional listenv ...")
+
+x <- listenv()
+length(x) <- 6
+dim(x) <- c(2,3)
+
+target <- parse_env_subset(x[[2]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$idx == 2, !target$exists)
+
+target <- parse_env_subset(x[[1,2]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$idx == 3, !target$exists)
+
+x[[1,2]] <- 1.2
+target <- parse_env_subset(x[[1,2]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$idx == 3, target$exists)
+
+target <- parse_env_subset(x[[1,4]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), is.na(target$idx), !target$exists)
+
+## Assert that x[[1,4]] is not the same as x[[c(1,4)]]
+target <- parse_env_subset(x[[1,4]], substitute=TRUE)
+str(target)
+target2 <- parse_env_subset(x[[c(1,4)]], substitute=TRUE)
+str(target2)
+target$code <- target2$code <- NULL
+stopifnot(!isTRUE(all.equal(target2, target)))
+
+
+dimnames(x) <- list(c("a", "b"), c("A", "B", "C"))
+print(x)
+
+target <- parse_env_subset(x[["a",2]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$idx == 3, target$exists)
+
+target <- parse_env_subset(x[["a","B"]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$idx == 3, target$exists)
+
+target <- parse_env_subset(x["a","B"], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$idx == 3, target$exists)
+
+target <- parse_env_subset(x["a",1:3], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), length(target$idx) == 3, all(target$idx == c(1,3,5)))
+
+target <- parse_env_subset(x["a",], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), length(target$idx) == 3, all(target$idx == c(1,3,5)))
+
+target <- parse_env_subset(x["a",-1], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), length(target$idx) == 2, all(target$idx == c(3,5)))
+
+message("*** parse_env_subset() on multi-dimensional listenv ... DONE")
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Exception handling
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+message("*** parse_env_subset() on multi-dimensional listenv - exceptions ...")
+
+x <- listenv()
+
+## Multidimensional subsetting on 'x' without dimensions
+res <- try(target <- parse_env_subset(x[[1,2]], substitute=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+## Multi-dimensional subsetting
+x <- listenv()
+length(x) <- 6
+dim(x) <- c(2,3)
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## FIXME: Should zero indices give parse errors or not?
+## - - - - - - - - - - - - - - - - - - - - - - - - - - -
+res <- try(target <- parse_env_subset(x[[0]], substitute=TRUE), silent=TRUE)
+## stopifnot(inherits(res, "try-error"))
+
+res <- try(target <- parse_env_subset(x[[1,0]], substitute=TRUE), silent=TRUE)
+## stopifnot(inherits(res, "try-error"))
+
+res <- try(target <- parse_env_subset(x[[1,2,3]], substitute=TRUE), silent=TRUE)
+## stopifnot(inherits(res, "try-error"))
+
+message("*** parse_env_subset() on multi-dimensional listenv - exceptions ... DONE")
+
+
+## Cleanup
+rm(list=setdiff(ls(envir=globalenv()), ovars), envir=globalenv())
diff --git a/tests/parse_env_subset.R b/tests/parse_env_subset.R
new file mode 100644
index 0000000..b151baa
--- /dev/null
+++ b/tests/parse_env_subset.R
@@ -0,0 +1,222 @@
+library("listenv")
+
+ovars <- ls(envir=globalenv())
+if (exists("x")) rm(list="x")
+if (exists("y")) rm(list="y")
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Variable in global/parent environment
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+message("*** parse_env_subset() on parent environment ...")
+
+target <- parse_env_subset(x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, environment()),
+ target$name == "x", is.na(target$idx), !target$exists)
+
+target <- parse_env_subset("x", substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, environment()),
+ target$name == "x", is.na(target$idx), !target$exists)
+
+x <- NULL
+target <- parse_env_subset(x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, environment()),
+ target$name == "x", is.na(target$idx), target$exists)
+
+target <- parse_env_subset(y, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, environment()),
+ target$name == "y", is.na(target$idx), !target$exists)
+
+message("*** parse_env_subset() on parent environment ... DONE")
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Environment
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+message("parse_env_subset() on environment ...")
+x <- new.env()
+
+target <- parse_env_subset(x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, environment()),
+ target$name == "x", is.na(target$idx), target$exists)
+
+target <- parse_env_subset(x$a, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), !target$exists)
+
+target <- parse_env_subset("a", envir=x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), !target$exists)
+
+target <- parse_env_subset(x[["a"]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), !target$exists)
+
+target <- parse_env_subset("a", envir=x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), !target$exists)
+
+res <- try(target <- parse_env_subset(1, substitute=FALSE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(target <- parse_env_subset(x[[1]], substitute=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+x$a <- 1
+target <- parse_env_subset(x$a, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), target$exists)
+
+message("parse_env_subset() on environment ... DONE")
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+## List environment
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+message("*** parse_env_subset() on listenv ...")
+
+x <- listenv()
+
+target <- parse_env_subset(x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, environment()),
+ target$name == "x", is.na(target$idx), target$exists)
+
+target <- parse_env_subset(x$a, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), !target$exists)
+
+target <- parse_env_subset(x[["a"]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), !target$exists)
+
+target <- parse_env_subset("a", envir=x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), !target$exists)
+
+target <- parse_env_subset(x[[1]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "", target$idx == 1, !target$exists)
+
+target <- parse_env_subset(x[[2]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "", target$idx == 2, !target$exists)
+
+x$a <- 1
+target <- parse_env_subset(x$a, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", target$idx == 1, target$exists)
+
+target <- parse_env_subset("a", envir=x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", target$idx == 1, target$exists)
+
+stopifnot(x$a == 1)
+stopifnot(x[[1]] == 1)
+
+target <- parse_env_subset(x[[1]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", target$idx == 1, target$exists)
+
+
+x[[3]] <- 3
+target <- parse_env_subset(x[[3]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "", target$idx == 3, target$exists)
+stopifnot(x[[3]] == 3)
+print(names(x))
+stopifnot(identical(names(x), c("a", "", "")))
+
+
+b <- 1
+target <- parse_env_subset(x[[b]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", target$idx == 1, target$exists)
+
+
+x <- listenv()
+length(x) <- 2
+
+target <- parse_env_subset(x[[1]], substitute=TRUE)
+str(target)
+stopifnot(!target$exists)
+
+target <- parse_env_subset(x[[2]], substitute=TRUE)
+str(target)
+stopifnot(!target$exists)
+
+target <- parse_env_subset(x[[3]], substitute=TRUE)
+str(target)
+stopifnot(!target$exists)
+stopifnot(length(x) == 2)
+
+x[[2]] <- 2
+target <- parse_env_subset(x[[2]], substitute=TRUE)
+str(target)
+stopifnot(target$exists)
+
+x[[4]] <- 4
+stopifnot(length(x) == 4)
+
+target <- parse_env_subset(x[[3]], substitute=TRUE)
+str(target)
+stopifnot(!target$exists)
+
+target <- parse_env_subset(x[1:5], substitute=TRUE)
+stopifnot(length(target$idx) == 5, all(target$idx == 1:5))
+str(target)
+
+target <- parse_env_subset(x[integer(0L)], substitute=TRUE)
+stopifnot(length(target$idx) == 0)
+str(target)
+
+target <- parse_env_subset(x[[integer(0L)]], substitute=TRUE)
+stopifnot(length(target$idx) == 0)
+str(target)
+
+target <- parse_env_subset(x[0], substitute=TRUE)
+stopifnot(length(target$idx) == 0)
+str(target)
+
+target <- parse_env_subset(x[-1], substitute=TRUE)
+stopifnot(length(target$idx) == length(x)-1)
+str(target)
+
+## Odds and ends
+target <- parse_env_subset(x[[""]], substitute=TRUE)
+stopifnot(length(target$idx) == 1L, !target$exists)
+
+message("*** parse_env_subset() on listenv ... DONE")
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Exception handling
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+message("*** parse_env_subset() - exceptions ...")
+
+x <- listenv()
+
+res <- try(target <- parse_env_subset(x[[0]], substitute=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(target <- parse_env_subset("_a", substitute=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(target <- parse_env_subset(1:10, envir=x, substitute=FALSE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(target <- parse_env_subset(c("a", "b"), envir=x, substitute=FALSE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(target <- parse_env_subset(x at a, substitute=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+message("*** parse_env_subset() - exceptions ... DONE")
+
+
+## Cleanup
+rm(list=setdiff(ls(envir=globalenv()), ovars), envir=globalenv())
diff --git a/tests/undim.R b/tests/undim.R
new file mode 100644
index 0000000..1ba6ac0
--- /dev/null
+++ b/tests/undim.R
@@ -0,0 +1,33 @@
+library("listenv")
+
+message("*** undim() ...")
+
+## General
+x <- c(a=1, b=2, A=3, B=4)
+names <- names(x)
+dim <- c(2,2)
+dimnames <- list(c("a", "b"), c("A", "B"))
+
+## Basic arrays
+y <- array(x, dim=dim, dimnames=dimnames)
+names(y) <- names
+z <- undim(y)
+stopifnot(identical(names(z), names))
+
+## Lists
+y <- as.list(x)
+dim(y) <- dim
+dimnames(y) <- dimnames
+names(y) <- names
+z <- undim(y)
+stopifnot(identical(names(z), names))
+
+## List environments
+y <- as.listenv(x)
+dim(y) <- dim
+dimnames(y) <- dimnames
+names(y) <- names
+z <- undim(y)
+stopifnot(identical(names(z), names))
+
+message("*** undim() ... DONE")
diff --git a/tests/utils.R b/tests/utils.R
new file mode 100644
index 0000000..cdd50d4
--- /dev/null
+++ b/tests/utils.R
@@ -0,0 +1,43 @@
+printf <- function(...) cat(sprintf(...))
+hpaste <- listenv:::hpaste
+
+# Some vectors
+x <- 1:6
+y <- 10:1
+z <- LETTERS[x]
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Abbreviation of output vector
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+printf("x = %s.\n", hpaste(x))
+## x = 1, 2, 3, ..., 6.
+
+printf("x = %s.\n", hpaste(x, maxHead=2))
+## x = 1, 2, ..., 6.
+
+printf("x = %s.\n", hpaste(x), maxHead=3) # Default
+## x = 1, 2, 3, ..., 6.
+
+# It will never output 1, 2, 3, 4, ..., 6
+printf("x = %s.\n", hpaste(x, maxHead=4))
+## x = 1, 2, 3, 4, 5 and 6.
+
+# Showing the tail
+printf("x = %s.\n", hpaste(x, maxHead=1, maxTail=2))
+## x = 1, ..., 5, 6.
+
+# Turning off abbreviation
+printf("y = %s.\n", hpaste(y, maxHead=Inf))
+## y = 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
+
+## ...or simply
+printf("y = %s.\n", paste(y, collapse=", "))
+## y = 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Adding a special separator before the last element
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Change last separator
+printf("x = %s.\n", hpaste(x, lastCollapse=" and "))
+## x = 1, 2, 3, 4, 5 and 6.
diff --git a/vignettes/listenv.md.rsp b/vignettes/listenv.md.rsp
new file mode 100644
index 0000000..c5e1de6
--- /dev/null
+++ b/vignettes/listenv.md.rsp
@@ -0,0 +1,360 @@
+<%@meta language="R-vignette" content="--------------------------------
+%\VignetteIndexEntry{List Environments}
+%\VignetteAuthor{Henrik Bengtsson}
+%\VignetteKeyword{R}
+%\VignetteKeyword{package}
+%\VignetteKeyword{vignette}
+%\VignetteKeyword{listenv}
+%\VignetteEngine{R.rsp::rsp}
+%\VignetteTangle{FALSE}
+--------------------------------------------------------------------"%>
+<%
+R.utils::use("R.utils")
+use("listenv")
+options("withCapture/newline"=FALSE)
+%>
+# <%@meta name="title"%>
+
+_List environments_ are environments that have list-like properties. They are implemented by the [listenv] package. The main features of a list environment are summarized in the below table:
+
+| Property | list environments | lists | environments |
+|------------------------------------------------------------------------|:-----------------:|:------:|:------------:|
+| Number of elements, e.g. `length()` | yes | yes | yes |
+| Named elements, e.g. `names()`, `x$a` and `x[["a"]]` | yes | yes | yes |
+| Duplicated names | yes | yes | |
+| Indexed elements, e.g. `x[[4]]` | yes | yes | |
+| Dimensions, e.g. `dim(x)` | yes | yes | |
+| Names of dimensions, e.g. `dimnames(x)` | yes | yes | |
+| Indexing by dimensions, e.g. `x[[2,4]]` and `x[[2,"D"]]` | yes | yes | |
+| Multi-element subsetting, e.g. `x[c("a", "c")]`, `x[-1]`, `[2:1,,3]` | yes | yes | |
+| Multi-element subsetting preserves element names | yes | | |
+| Removing element by assigning NULL, e.g. `x$c <- NULL` | yes | yes | |
+| Mutable, e.g. `y <- x; y$a <- 3; identical(y, x)` | yes | | yes |
+| Compatible* with `assign()`, `delayedAssign()`, `get()` and `exists()` | yes | | yes |
+
+For example,
+```r
+<%=withCapture({
+x <- listenv(a=1, b=2, c="hello")
+x
+
+length(x)
+names(x)
+x$a
+x[[3]] <- toupper(x[[3]])
+x$c
+
+y <- x
+y$d <- y$a + y[["b"]]
+names(y)[2] <- "a"
+y$a
+y
+identical(y, x)
+
+for (ii in seq_along(x)) {
+ cat(sprintf("Element %d (%s): %s\n", ii, sQuote(names(x)[ii]), x[[ii]]))
+}
+<%---
+get(map(x)["b"], envir=x)
+assign(map(x)["b"], 3, envir=x)
+---%>
+
+x[c(1,3)] <- list(2, "Hello world!")
+
+x
+y <- as.list(x)
+str(y)
+z <- as.listenv(y)
+z
+identical(z, x)
+all.equal(z, x)
+})%>
+```
+
+## Creating list environments
+List environments are created similarly to lists but also similarly to environments. To create an empty list environment, use
+```r
+<%=withCapture({
+x <- listenv()
+x
+})%>
+```
+This can later can be populated using named assignments,
+```r
+<%=withCapture({
+x$a <- 1
+x
+})%>
+```
+comparable to how both lists and environments work. Similarly to lists, they can also be populated using indices, e.g.
+```r
+<%=withCapture({
+x[[2]] <- 2
+x$c <- 3
+x
+})%>
+```
+Just as for lists, a list environment is expanded with `NULL` elements whenever a new element is added that is beyond the current length plus one, e.g.
+```r
+<%=withCapture({
+x[[5]] <- 5
+x
+x[[4]]
+})%>
+```
+
+As with lists, the above list environment can also be created from the start, e.g.
+```r
+<%=withCapture({
+x <- listenv(a=1, 3, c=4, NULL, 5)
+x
+})%>
+```
+
+
+As for lists, the length of a list environment can at any time be increased or decreased by assigning it a new length.
+If decreased, elements are dropped, e.g.
+```r
+<%=withCapture({
+x
+length(x) <- 2
+x
+x[[1]]
+x[[2]]
+})%>
+```
+If increased, new elements are populated with unnamed elements of `NULL`, e.g.
+```r
+<%=withCapture({
+length(x) <- 4
+x
+x[[3]]
+x[[4]]
+})%>
+```
+
+To allocate an "empty" list environment (with all `NULL`:s) of a given length, do
+```r
+<%=withCapture({
+x <- listenv()
+length(x) <- 4
+x
+})%>
+```
+_Note_: Unfortunately, it is _not_ possible to use `x <- vector("listenv", length=4)`; that construct is only supported for the basic data types.
+
+
+
+## Iterating over elements
+
+### Iterating over elements by names
+Analogously to lists and plain environments, it is possible to iterate over elements of list environments by the element names. For example,
+```r
+<%=withCapture({
+x <- listenv(a=1, b=2, c=3)
+for (name in names(x)) {
+ cat(sprintf("Element %s: %s\n", sQuote(name), x[[name]]))
+}
+})%>
+```
+
+### Iterating over elements by indices
+Analogously to lists, but contrary to plain environments, it is also possible to iterate over elements by their indices. For example,
+```r
+<%=withCapture({
+x <- listenv(a=1, b=2, c=3)
+for (ii in seq_along(x)) {
+ cat(sprintf("Element %d: %s\n", ii, x[[ii]]))
+}
+})%>
+```
+
+
+## Coercion to and from list environments
+
+### Coercing to lists and vectors
+
+Coercing a list environment to a list:
+```r
+<%=withCapture({
+x <- listenv(a=2, b=3, c="hello")
+x
+y <- as.list(x)
+str(y)
+})%>
+```
+
+Coercing a list to a list environment:
+```r
+<%=withCapture({
+z <- as.listenv(y)
+z
+identical(z, x)
+all.equal(z, x)
+})%>
+```
+
+Unlisting:
+```r
+<%=withCapture({
+unlist(x)
+unlist(x[-3])
+unlist(x[1:2], use.names=FALSE)
+})%>
+```
+
+
+## Multi-dimensional list environments
+
+Analogously to lists, and contrary to plain environments, list environments can have dimensions with corresponding names. For example,
+```r
+<%=withCapture({
+x <- as.listenv(1:6)
+dim(x) <- c(2,3)
+dimnames(x) <- list(c("a", "b"), c("A", "B","C"))
+x
+})%>
+```
+An easy way to quickly get an overview is to coerce to a list, e.g.
+```r
+<%=withCapture({
+as.list(x)
+})%>
+```
+Individual elements of a list environment can be accessed using standard subsetting syntax, e.g.
+```r
+<%=withCapture({
+x[["a", "B"]]
+x[[1, 2]]
+x[[1, "B"]]
+})%>
+```
+We can assign individual elements similarly, e.g.
+```r
+<%=withCapture({
+x[["b", "B"]] <- -x[["b", "B"]]
+as.list(x)
+})%>
+```
+We can also assign multiple elements through dimensional subsetting, e.g.
+```r
+<%=withCapture({
+x[2,-1] <- 98:99
+as.list(x)
+x["a",c(1,3)] <- list(97, "foo")
+as.list(x)
+x[] <- 1:6
+as.list(x)
+})%>
+```
+
+
+Concurrently with dimensional names it is possible to have names of the invidual elements just as for list environments without dimensions. For example,
+```r
+<%=withCapture({
+names(x) <- letters[seq_along(x)]
+x
+x[["a"]]
+x[["f"]]
+x[c("a", "f")]
+unlist(x)
+})%>
+```
+Contrary to lists, element names are preserved also with multi-dimensional subsetting, e.g.
+```r
+<%=withCapture({
+x[1,2]
+x[1,2,drop=FALSE]
+x[1:2,2:1]
+x[2,]
+x[2,,drop=FALSE]
+x["b",-2,drop=FALSE]
+})%>
+```
+
+
+Note, whenever dimensions are set using `dim(x) <- dims` both the dimensional names and the element names are removed, e.g.
+```r
+> dim(x) <- NULL
+> names(x)
+NULL
+```
+This behavior is by design, cf. `help("dim", package="base")`.
+<%---
+Because of this, the listenv package provides the `undim()` function, which removes the dimensions but preserves the names, e.g.
+```r
+<%=withCapture({
+x <- undim(x)
+names(x)
+})%>
+```
+_Warning_: Since list environments _and their attributes_ are mutable, calling
+```r
+undim(x)
+```
+will have the same effect as
+```r
+x <- undim(x)
+```
+That is, the dimension attributes of `x` will be changed. The reason for this is explained in Section 'Important about environments' above.
+---%>
+
+### Limitations
+The current implementation does _not_ support _dimensional subsetting_ of more than one element. For instance, `x[1,]` is not supported by this version.
+
+
+
+## Important about environments
+List environments are as their name suggests _environments_. Whenever working with environments in R, it is important to understand that _environments are mutable_ whereas all other of the basic data types in R are immutable. For example, consider the following function that assigns zero to element `a` of object `x`:
+```r
+<%=withCapture({
+setA <- function(x) {
+ x$a <- 0
+ x
+}
+})%>
+```
+If we pass a regular list to this function,
+```r
+<%=withCapture({
+x <- list(a=1)
+y <- setA(x)
+x$a
+y$a
+})%>
+```
+we see that `x` is unaffected by the assignment. This is because _lists are immutable_ in R. However, if we pass an environment instead,
+```r
+<%=withCapture({
+x <- new.env()
+x$a <- 1
+y <- setA(x)
+x$a
+y$a
+})%>
+```
+we find that `x` was affected by the assignment. This is because _environments are mutable_ in R. Since list environments inherits from environments, this also goes for them, e.g.
+```r
+<%=withCapture({
+x <- listenv(a=1)
+y <- setA(x)
+x$a
+y$a
+})%>
+```
+
+What is also important to understand is that it is not just the _content_ of an environment that is mutable but also its _attributes_. For example,
+```r
+<%=withCapture({
+x <- listenv(a=1)
+y <- x
+attr(y, "foo") <- "Hello!"
+attr(x, "foo")
+})%>
+```
+
+
+[listenv]: http://cran.r-project.org/package=listenv
+
+---
+Copyright Henrik Bengtsson, 2015
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/r-cran-listenv.git
More information about the debian-med-commit
mailing list