Skip to content

Commit

Permalink
start preparing for a potential CRAN release
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinushey committed Aug 20, 2024
1 parent 5587bae commit c235aba
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 21 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Description: Enables destructuring assignments in R, through the use of a
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
RoxygenNote: 7.2.3
RoxygenNote: 7.3.2
Suggests:
codetools,
testthat (>= 3.0.0)
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
YEAR: 2023
YEAR: 2024
COPYRIGHT HOLDER: Kevin Ushey
47 changes: 40 additions & 7 deletions R/dot.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,36 @@

dot <- as.name(".")
dotdot <- as.name("..")

#' The Destructuring Dot Operator
#'
#' Use `dotty` to performed destructuring assignments.
#' Please see the examples below for usages.
#'
#' @examples
#'
#' # extract number of rows, number of columns from mtcars
#' .[nr, nc] <- dim(mtcars)
#' c(nr, nc)
#'
#' # extract first, last element of vector
#' .[first, .., last] <- c(1, 2, 3, 4, 5)
#' c(first, last)
#'
#' # extract a value by name
#' .[beta = beta] <- list(alpha = 1, beta = 2, gamma = 3)
#' beta
#'
#' # unpack nested values
#' .[x, .[y, .[z]]] <- list(1, list(2, list(3)))
#' c(x, y, z)
#'
#' # split version components
#' .[major, minor, patch] <- getRversion()
#' c(major, minor, patch)
#'
#' @aliases dotty
#' @rdname dotty
#' @export
. <- structure(list(), class = "dotty")

Expand All @@ -25,28 +55,31 @@ dotdot <- as.name("..")

dotty <- function(parts, value, envir) {

# search for a '..' placeholder; if we
# search for a '..' placeholder -- if none exists,
# then just run dotty on the whole vector
index <- dotty_find(parts)
if (is.null(index)) {
dotty_impl(parts, value, envir)
return(.)
}

# we had a '..' placeholder; split the expression into
# lhs and rhs parts, and apply on each side
# split into left parts, right parts
nleft <- index - 1L
nright <- length(parts) - index + 1L
nlhs <- index - 1L
nrhs <- length(parts) - index + 1L

# evaluate left variables
dotty_impl(
head(parts, n = nleft),
head(value, n = nleft),
head(parts, n = nlhs),
head(value, n = nlhs),
envir
)

# evaluate right variables
dotty_impl(
tail(parts, n = nright),
tail(value, n = nright),
tail(parts, n = nrhs),
tail(value, n = nrhs),
envir
)

Expand Down
32 changes: 28 additions & 4 deletions R/dotify.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@

`%||%` <- function(x, y) {
if (is.null(x)) y else x
}

dotify <- function() {

# allow us to be disabled if necessary
enabled <- Sys.getenv("DOTTY_DOTIFY_ENABLED", unset = "TRUE")
if (!as.logical(enabled))
return()

# use some .onLoad() hacks to tell codetools how to parse our code
if (!requireNamespace("codetools", quietly = TRUE))
return()
Expand All @@ -11,6 +20,16 @@ dotify <- function() {
if (!is.environment(handlers))
return()

# make sure we have 'isUtilsVar'
if (!exists("isUtilsVar", envir = codetools))
return()

# check if 'isUtilsVar' has changed in an unexpected way
isUtilsVar <- codetools$isUtilsVar
expected <- pairlist(v = quote(expr = ), env = quote(expr = ))
if (!identical(formals(isUtilsVar), expected))
return()

# tell codetools to accept our code's handlers
# TODO: ask Luke nicely to allow us to do this
hack <- function(v, env) TRUE
Expand All @@ -20,10 +39,16 @@ dotify <- function() {
.BaseNamespaceEnv$lockBinding("isUtilsVar", env = codetools)

# add our handler for subset-assignment
handler <- handlers$`[<-`
handler <- handlers$`[<-` %||% function(v, w) {}
handlers$`[<-` <- function(v, w) {

# get defined variables
# only handle dotty calls
if (!identical(v[[2L]], dot)) {
handler(v, w)
return()
}

# start adding them as local variables
vars <- character()
for (i in seq_along(v)) {
if (is.symbol(v[[i]])) {
Expand All @@ -35,8 +60,7 @@ dotify <- function() {
w$startCollectLocals(vars, character(), w)

# call original handler
if (!is.null(handler))
handler(v, w)
handler(v, w)

}

Expand Down
14 changes: 6 additions & 8 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ c(x, y, z)
# split version components
.[major, minor, patch] <- getRversion()
c(major, minor, patch)
# compute on values in object -- this is probably too magical
.[total = sum(a)] <- list(a = 1:5)
total
```


Expand All @@ -43,9 +39,11 @@ If you'd like to use `dotty` in your CRAN package, you can avoid
`R CMD check` warnings by including a file called `R/zzz.R` with the contents:

```
dotty::dotify()
.onLoad <- function(libname, pkgname) {
dotty::dotify()
}
```

This function will search for usages of `dotty`, and call
`utils::globalVariables()` to assert to `R CMD check` that such variables
are valid for use.
This function patches [codetools](https://cran.r-project.org/package=codetools)
so that variables usages in `.` expressions can be linted as though those
were regular bindings / assignments.
41 changes: 41 additions & 0 deletions man/dotty.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit c235aba

Please sign in to comment.