Skip to content

Commit d278046

Browse files
committed
bring get_code_dependency
1 parent 2c580e9 commit d278046

File tree

6 files changed

+1232
-8
lines changed

6 files changed

+1232
-8
lines changed

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# teal.code 0.5.0.9010
22

3+
### Enhancements
4+
5+
* `get_code()` was extended with `names` parameter and allows the code extraction to be limited to objects stored in
6+
`qenv` but limited to `names`.
7+
38
# teal.code 0.5.0
49

510
### Breaking Change

R/qenv-get_code.R

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,97 @@
55
#'
66
#' @param object (`qenv`)
77
#' @param deparse (`logical(1)`) flag specifying whether to return code as `character` or `expression`.
8+
#' @param names `r lifecycle::badge("experimental")` (`character`) vector of object names to return the code for.
9+
#' For more details see the "Extracting dataset-specific code" section.
810
#' @param ... see `Details`
911
#'
12+
#'
13+
#' @section Extracting dataset-specific code:
14+
#' When `names` is specified, the code returned will be limited to the lines needed to _create_
15+
#' the requested objects. The code stored in the `@code` slot is analyzed statically to determine
16+
#' which lines the objects of interest depend upon. The analysis works well when objects are created
17+
#' with standard infix assignment operators (see `?assignOps`) but it can fail in some situations.
18+
#'
19+
#' Consider the following examples:
20+
#'
21+
#' _Case 1: Usual assignments._
22+
#' ```r
23+
#' q1 <- qenv() |>
24+
#' within({
25+
#' foo <- function(x) {
26+
#' x + 1
27+
#' }
28+
#' x <- 0
29+
#' y <- foo(x)
30+
#' })
31+
#' get_code(q1, names = "y")
32+
#' ```
33+
#' `x` has no dependencies, so `get_code(data, names = "x")` will return only the second call.\cr
34+
#' `y` depends on `x` and `foo`, so `get_code(data, names = "y")` will contain all three calls.
35+
#'
36+
#' _Case 2: Some objects are created by a function's side effects._
37+
#' ```r
38+
#' q2 <- qenv() |>
39+
#' within({
40+
#' foo <- function() {
41+
#' x <<- x + 1
42+
#' }
43+
#' x <- 0
44+
#' foo()
45+
#' y <- x
46+
#' })
47+
#' get_code(q2, names = "y")
48+
#' ```
49+
#' Here, `y` depends on `x` but `x` is modified by `foo` as a side effect (not by reassignment)
50+
#' and so `get_code(data, names = "y")` will not return the `foo()` call.\cr
51+
#' To overcome this limitation, code dependencies can be specified manually.
52+
#' Lines where side effects occur can be flagged by adding "`# @linksto <object name>`" at the end.\cr
53+
#' Note that `within` evaluates code passed to `expr` as is and comments are ignored.
54+
#' In order to include comments in code one must use the `eval_code` function instead.
55+
#'
56+
#' ```r
57+
#' q3 <- qenv() |>
58+
#' eval_code("
59+
#' foo <- function() {
60+
#' x <<- x + 1
61+
#' }
62+
#' x <- 0
63+
#' foo() # @linksto x
64+
#' y <- x
65+
#' ")
66+
#' get_code(q3, names = "y")
67+
#' ```
68+
#' Now the `foo()` call will be properly included in the code required to recreate `y`.
69+
#'
70+
#' Note that two functions that create objects as side effects, `assign` and `data`, are handled automatically.
71+
#'
72+
#' Here are known cases where manual tagging is necessary:
73+
#' - non-standard assignment operators, _e.g._ `%<>%`
74+
#' - objects used as conditions in `if` statements: `if (<condition>)`
75+
#' - objects used to iterate over in `for` loops: `for(i in <sequence>)`
76+
#' - creating and evaluating language objects, _e.g._ `eval(<call>)`
77+
#'
1078
#' @return
1179
#' `get_code` returns the traced code (from `@code` slot) in the form specified by `deparse`.
1280
#'
1381
#' @examples
1482
#' # retrieve code
83+
#' q <- within(qenv(), {a <- 1; b <- 2})
1584
#' get_code(q)
1685
#' get_code(q, deparse = FALSE)
86+
#' get_code(q, names = "a")
87+
#'
88+
#' q <- qenv()
89+
#' q <- eval_code(q, code = c("a <- 1", "b <- 2"))
90+
#' get_code(q, names = "a")
1791
#'
1892
#' @name get_code
1993
#' @rdname qenv
2094
#' @aliases get_code,qenv-method
2195
#' @aliases get_code,qenv.error-method
2296
#'
2397
#' @export
24-
setGeneric("get_code", function(object, deparse = TRUE, ...) {
98+
setGeneric("get_code", function(object, deparse = TRUE, names = NULL, ...) {
2599
# this line forces evaluation of object before passing to the generic
26100
# needed for error handling to work properly
27101
grDevices::pdf(nullfile())
@@ -31,16 +105,24 @@ setGeneric("get_code", function(object, deparse = TRUE, ...) {
31105
standardGeneric("get_code")
32106
})
33107

34-
setMethod("get_code", signature = "qenv", function(object, deparse = TRUE) {
108+
setMethod("get_code", signature = "qenv", function(object, deparse = TRUE, names = NULL, ...) {
35109
checkmate::assert_flag(deparse)
110+
checkmate::assert_character(names, min.len = 1L, null.ok = TRUE)
111+
112+
code <- if (!is.null(names)) {
113+
get_code_dependency(object@code, names, ...)
114+
} else {
115+
object@code
116+
}
117+
36118
if (deparse) {
37-
if (length(object@code) == 0) {
38-
object@code
119+
if (length(code) == 0) {
120+
code
39121
} else {
40-
paste(object@code, collapse = "\n")
122+
paste(code, collapse = "\n")
41123
}
42124
} else {
43-
parse(text = paste(c("{", object@code, "}"), collapse = "\n"), keep.source = TRUE)
125+
parse(text = paste(c("{", code, "}"), collapse = "\n"), keep.source = TRUE)
44126
}
45127
})
46128

0 commit comments

Comments
 (0)