From fb5cf2b799501836b3f519442ff30b76597b7011 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Tue, 27 May 2025 12:05:39 +0200 Subject: [PATCH 01/15] eval_code(cache --- R/qenv-eval_code.R | 19 ++++++++++++------- man/eval_code.Rd | 10 ++++++---- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 14af1ae7..0faea8b0 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -9,6 +9,9 @@ #' @param code (`character`, `language` or `expression`) code to evaluate. #' It is possible to preserve original formatting of the `code` by providing a `character` or an #' `expression` being a result of `parse(keep.source = TRUE)`. +#' @param cache (`logical(1)`) whether to cache returned value of the code evaluation. +#' +#' @param ... ([`dots`]) additional arguments passed to future methods. #' #' @return #' `qenv` environment with `code/expr` evaluated or `qenv.error` if evaluation fails. @@ -27,9 +30,9 @@ #' @aliases eval_code,qenv.error,ANY-method #' #' @export -setGeneric("eval_code", function(object, code) standardGeneric("eval_code")) +setGeneric("eval_code", function(object, code, cache = FALSE, ...) standardGeneric("eval_code")) -setMethod("eval_code", signature = c("qenv", "character"), function(object, code) { +setMethod("eval_code", signature = c("qenv", "character"), function(object, code, cache = FALSE, ...) { parsed_code <- parse(text = code, keep.source = TRUE) object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(.GlobalEnv)) if (length(parsed_code) == 0) { @@ -42,13 +45,15 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code for (i in seq_along(code_split)) { current_code <- code_split[[i]] current_call <- parse(text = current_code, keep.source = TRUE) - # Using withCallingHandlers to capture warnings and messages. # Using tryCatch to capture the error and abort further evaluation. x <- withCallingHandlers( tryCatch( { - eval(current_call, envir = object@.xData) + out <- eval(current_call, envir = object@.xData) + if (cache && i == seq_along(code_split)) { + attr(current_code, "cache") <- out + } if (!identical(parent.env(object@.xData), parent.env(.GlobalEnv))) { # needed to make sure that @.xData is always a sibling of .GlobalEnv # could be changed when any new package is added to search path (through library or require call) @@ -89,11 +94,11 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code object }) -setMethod("eval_code", signature = c("qenv", "language"), function(object, code) { +setMethod("eval_code", signature = c("qenv", "language"), function(object, code, cache = FALSE, ...) { eval_code(object, code = paste(vapply(lang2calls(code), deparse1, collapse = "\n", character(1L)), collapse = "\n")) }) -setMethod("eval_code", signature = c("qenv", "expression"), function(object, code) { +setMethod("eval_code", signature = c("qenv", "expression"), function(object, code, cache = FALSE, ...) { srcref <- attr(code, "wholeSrcref") if (length(srcref)) { eval_code(object, code = paste(attr(code, "wholeSrcref"), collapse = "\n")) @@ -109,7 +114,7 @@ setMethod("eval_code", signature = c("qenv", "expression"), function(object, cod } }) -setMethod("eval_code", signature = c("qenv.error", "ANY"), function(object, code) { +setMethod("eval_code", signature = c("qenv.error", "ANY"), function(object, code, cache = FALSE, ...) { object }) diff --git a/man/eval_code.Rd b/man/eval_code.Rd index 203a96af..eff9d6e2 100644 --- a/man/eval_code.Rd +++ b/man/eval_code.Rd @@ -9,7 +9,7 @@ \alias{within.qenv} \title{Evaluate code in \code{qenv}} \usage{ -eval_code(object, code) +eval_code(object, code, cache = FALSE, ...) \method{within}{qenv}(data, expr, ...) } @@ -20,12 +20,14 @@ eval_code(object, code) It is possible to preserve original formatting of the \code{code} by providing a \code{character} or an \code{expression} being a result of \code{parse(keep.source = TRUE)}.} -\item{data}{(\code{qenv})} - -\item{expr}{(\code{expression}) to evaluate. Must be inline code, see \verb{Using language objects...}} +\item{cache}{(\code{logical(1)}) whether to cache returned value of the code evaluation.} \item{...}{named argument value will substitute a symbol in the \code{expr} matched by the name. For practical usage see Examples section below.} + +\item{data}{(\code{qenv})} + +\item{expr}{(\code{expression}) to evaluate. Must be inline code, see \verb{Using language objects...}} } \value{ \code{qenv} environment with \code{code/expr} evaluated or \code{qenv.error} if evaluation fails. From 6971dca5aa8f89b8ff7bf9267b1429d4791d78b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 28 May 2025 13:51:33 +0100 Subject: [PATCH 02/15] chore: eval_code methods no longer depend on second argument --- R/qenv-eval_code.R | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 0faea8b0..3cb9ed56 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -32,7 +32,9 @@ #' @export setGeneric("eval_code", function(object, code, cache = FALSE, ...) standardGeneric("eval_code")) -setMethod("eval_code", signature = c("qenv", "character"), function(object, code, cache = FALSE, ...) { +setMethod("eval_code", signature = c("qenv"), function(object, code, cache = FALSE, ...) { + logger::log_fatal("eval_code with ANY") + code <- .preprocess_code(code) # preprocess code to ensure it is a character vector parsed_code <- parse(text = code, keep.source = TRUE) object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(.GlobalEnv)) if (length(parsed_code) == 0) { @@ -51,7 +53,7 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code tryCatch( { out <- eval(current_call, envir = object@.xData) - if (cache && i == seq_along(code_split)) { + if (cache && i == length(code_split)) { attr(current_code, "cache") <- out } if (!identical(parent.env(object@.xData), parent.env(.GlobalEnv))) { @@ -94,11 +96,9 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code object }) -setMethod("eval_code", signature = c("qenv", "language"), function(object, code, cache = FALSE, ...) { - eval_code(object, code = paste(vapply(lang2calls(code), deparse1, collapse = "\n", character(1L)), collapse = "\n")) -}) setMethod("eval_code", signature = c("qenv", "expression"), function(object, code, cache = FALSE, ...) { + logger::log_fatal("eval_code with expression") srcref <- attr(code, "wholeSrcref") if (length(srcref)) { eval_code(object, code = paste(attr(code, "wholeSrcref"), collapse = "\n")) @@ -114,6 +114,18 @@ setMethod("eval_code", signature = c("qenv", "expression"), function(object, cod } }) +setGeneric(".preprocess_code", function(code) standardGeneric(".preprocess_code")) + +setMethod(".preprocess_code", signature = c("ANY"), function(code) { + logger::log_warn("process character") + as.character(code) +}) + +setMethod(".preprocess_code", signature = c("language"), function(code) { + logger::log_warn("process language") + paste(vapply(lang2calls(code), deparse1, collapse = "\n", character(1L))) +}) + setMethod("eval_code", signature = c("qenv.error", "ANY"), function(object, code, cache = FALSE, ...) { object }) From 7abc267d0ff520a88e1b594c3fb569f2f7b39203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 28 May 2025 13:54:35 +0100 Subject: [PATCH 03/15] cleanup: remove logger calls --- R/qenv-eval_code.R | 4 ---- 1 file changed, 4 deletions(-) diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 3cb9ed56..4bd0bba5 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -33,7 +33,6 @@ setGeneric("eval_code", function(object, code, cache = FALSE, ...) standardGeneric("eval_code")) setMethod("eval_code", signature = c("qenv"), function(object, code, cache = FALSE, ...) { - logger::log_fatal("eval_code with ANY") code <- .preprocess_code(code) # preprocess code to ensure it is a character vector parsed_code <- parse(text = code, keep.source = TRUE) object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(.GlobalEnv)) @@ -98,7 +97,6 @@ setMethod("eval_code", signature = c("qenv"), function(object, code, cache = FAL setMethod("eval_code", signature = c("qenv", "expression"), function(object, code, cache = FALSE, ...) { - logger::log_fatal("eval_code with expression") srcref <- attr(code, "wholeSrcref") if (length(srcref)) { eval_code(object, code = paste(attr(code, "wholeSrcref"), collapse = "\n")) @@ -117,12 +115,10 @@ setMethod("eval_code", signature = c("qenv", "expression"), function(object, cod setGeneric(".preprocess_code", function(code) standardGeneric(".preprocess_code")) setMethod(".preprocess_code", signature = c("ANY"), function(code) { - logger::log_warn("process character") as.character(code) }) setMethod(".preprocess_code", signature = c("language"), function(code) { - logger::log_warn("process language") paste(vapply(lang2calls(code), deparse1, collapse = "\n", character(1L))) }) From a075fce099e17bdb40c3fcfcda16e77b068444f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Wed, 28 May 2025 17:55:56 +0100 Subject: [PATCH 04/15] cleanup: remove method from eval_code and removal of unecessary functions --- R/qenv-eval_code.R | 72 +++++++++++++-------------------- tests/testthat/test-qenv_join.R | 4 +- 2 files changed, 29 insertions(+), 47 deletions(-) diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 4bd0bba5..15511053 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -32,8 +32,29 @@ #' @export setGeneric("eval_code", function(object, code, cache = FALSE, ...) standardGeneric("eval_code")) -setMethod("eval_code", signature = c("qenv"), function(object, code, cache = FALSE, ...) { +setMethod("eval_code", signature = c(object = "qenv", code = "ANY"), function(object, code, cache = FALSE, ...) { code <- .preprocess_code(code) # preprocess code to ensure it is a character vector + srcref <- attr(code, "wholeSrcref") + if (is.expression(code) && length(srcref) == 0L) { + result <- Reduce(function(u, v) { + if (inherits(v, "=") && identical(typeof(v), "language")) { + # typeof(`=`) is language, but it doesn't dispatch on it, so we need to + # explicitly pass it as first class of the object + class(v) <- unique(c("language", class(v))) + } + .eval_code(u, v, cache = FALSE, ...) + }, init = object, x = code) + return(result) + } else if (is.expression(code)) { + code <- paste(attr(code, "wholeSrcref"), collapse = "\n") + } + .eval_code(object = object, code = code, cache = cache, ...) +}) + +setMethod("eval_code", signature = c("qenv.error", "ANY"), function(object, code, cache = FALSE, ...) object) + +#' @keywords internal +.eval_code <- function(object, code, cache = FALSE, ...) { parsed_code <- parse(text = code, keep.source = TRUE) object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(.GlobalEnv)) if (length(parsed_code) == 0) { @@ -66,7 +87,7 @@ setMethod("eval_code", signature = c("qenv"), function(object, code, cache = FAL errorCondition( message = sprintf( "%s \n when evaluating qenv code:\n%s", - .ansi_strip(conditionMessage(e)), + cli::ansi_strip(conditionMessage(e)), current_code ), class = c("qenv.error", "try-error", "simpleError"), @@ -75,11 +96,11 @@ setMethod("eval_code", signature = c("qenv"), function(object, code, cache = FAL } ), warning = function(w) { - attr(current_code, "warning") <<- .ansi_strip(sprintf("> %s\n", conditionMessage(w))) + attr(current_code, "warning") <<- cli::ansi_strip(sprintf("> %s\n", conditionMessage(w))) invokeRestart("muffleWarning") }, message = function(m) { - attr(current_code, "message") <<- .ansi_strip(sprintf("> %s", conditionMessage(m))) + attr(current_code, "message") <<- cli::ansi_strip(sprintf("> %s", conditionMessage(m))) invokeRestart("muffleMessage") } ) @@ -93,49 +114,10 @@ setMethod("eval_code", signature = c("qenv"), function(object, code, cache = FAL lockEnvironment(object@.xData, bindings = TRUE) object -}) - - -setMethod("eval_code", signature = c("qenv", "expression"), function(object, code, cache = FALSE, ...) { - srcref <- attr(code, "wholeSrcref") - if (length(srcref)) { - eval_code(object, code = paste(attr(code, "wholeSrcref"), collapse = "\n")) - } else { - Reduce(function(u, v) { - if (inherits(v, "=") && identical(typeof(v), "language")) { - # typeof(`=`) is language, but it doesn't dispatch on it, so we need to - # explicitly pass it as first class of the object - class(v) <- unique(c("language", class(v))) - } - eval_code(u, v) - }, init = object, x = code) - } -}) +} setGeneric(".preprocess_code", function(code) standardGeneric(".preprocess_code")) - -setMethod(".preprocess_code", signature = c("ANY"), function(code) { - as.character(code) -}) - +setMethod(".preprocess_code", signature = c("ANY"), function(code) as.character(code)) setMethod(".preprocess_code", signature = c("language"), function(code) { paste(vapply(lang2calls(code), deparse1, collapse = "\n", character(1L))) }) - -setMethod("eval_code", signature = c("qenv.error", "ANY"), function(object, code, cache = FALSE, ...) { - object -}) - -# if cli is installed rlang adds terminal printing characters -# which need to be removed -.ansi_strip <- function(chr) { - if (requireNamespace("cli", quietly = TRUE)) { - cli::ansi_strip(chr) - } else { - chr - } -} - -get_code_attr <- function(qenv, attr) { - unlist(lapply(qenv@code, function(x) attr(x, attr))) -} diff --git a/tests/testthat/test-qenv_join.R b/tests/testthat/test-qenv_join.R index 9a5d356b..a234fced 100644 --- a/tests/testthat/test-qenv_join.R +++ b/tests/testthat/test-qenv_join.R @@ -131,7 +131,7 @@ testthat::test_that("Joining two independent qenvs with warnings results in obje q <- c(q1, q2) testthat::expect_equal( - unname(get_code_attr(q, "warning")), + vapply(q@code, attr, which = "warning", character(1L), USE.NAMES = FALSE), c( "> This is warning 1\n", "> This is warning 2\n" @@ -146,7 +146,7 @@ testthat::test_that("Joining two independent qenvs with messages results in obje q <- c(q1, q2) testthat::expect_equal( - unname(get_code_attr(q, "message")), + vapply(q@code, attr, which = "message", character(1L), USE.NAMES = FALSE), c( "> This is message 1\n", "> This is message 2\n" From 78396d6dc6742466a80c2829f89b7450469c0df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Mon, 2 Jun 2025 11:19:07 +0100 Subject: [PATCH 05/15] chore: remove code dispatch parameter in eval_code --- R/qenv-eval_code.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 15511053..46d192f1 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -32,7 +32,7 @@ #' @export setGeneric("eval_code", function(object, code, cache = FALSE, ...) standardGeneric("eval_code")) -setMethod("eval_code", signature = c(object = "qenv", code = "ANY"), function(object, code, cache = FALSE, ...) { +setMethod("eval_code", signature = c(object = "qenv"), function(object, code, cache = FALSE, ...) { code <- .preprocess_code(code) # preprocess code to ensure it is a character vector srcref <- attr(code, "wholeSrcref") if (is.expression(code) && length(srcref) == 0L) { @@ -51,7 +51,7 @@ setMethod("eval_code", signature = c(object = "qenv", code = "ANY"), function(ob .eval_code(object = object, code = code, cache = cache, ...) }) -setMethod("eval_code", signature = c("qenv.error", "ANY"), function(object, code, cache = FALSE, ...) object) +setMethod("eval_code", signature = c(object = "qenv.error"), function(object, code, cache = FALSE, ...) object) #' @keywords internal .eval_code <- function(object, code, cache = FALSE, ...) { From bb1ab119326c2537b86075d8b7dfef5cb281d0b2 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Tue, 3 Jun 2025 05:36:01 +0200 Subject: [PATCH 06/15] fix cache for expression and tidyup --- R/qenv-eval_code.R | 33 +++++++++++++-------------------- man/eval_code.Rd | 5 +---- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 46d192f1..91a89923 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -24,30 +24,13 @@ #' q <- eval_code(q, quote(library(checkmate))) #' q <- eval_code(q, expression(assert_number(a))) #' -#' @aliases eval_code,qenv,character-method -#' @aliases eval_code,qenv,language-method -#' @aliases eval_code,qenv,expression-method -#' @aliases eval_code,qenv.error,ANY-method +#' @aliases eval_code,qenv-method #' #' @export setGeneric("eval_code", function(object, code, cache = FALSE, ...) standardGeneric("eval_code")) setMethod("eval_code", signature = c(object = "qenv"), function(object, code, cache = FALSE, ...) { code <- .preprocess_code(code) # preprocess code to ensure it is a character vector - srcref <- attr(code, "wholeSrcref") - if (is.expression(code) && length(srcref) == 0L) { - result <- Reduce(function(u, v) { - if (inherits(v, "=") && identical(typeof(v), "language")) { - # typeof(`=`) is language, but it doesn't dispatch on it, so we need to - # explicitly pass it as first class of the object - class(v) <- unique(c("language", class(v))) - } - .eval_code(u, v, cache = FALSE, ...) - }, init = object, x = code) - return(result) - } else if (is.expression(code)) { - code <- paste(attr(code, "wholeSrcref"), collapse = "\n") - } .eval_code(object = object, code = code, cache = cache, ...) }) @@ -117,7 +100,17 @@ setMethod("eval_code", signature = c(object = "qenv.error"), function(object, co } setGeneric(".preprocess_code", function(code) standardGeneric(".preprocess_code")) -setMethod(".preprocess_code", signature = c("ANY"), function(code) as.character(code)) +setMethod(".preprocess_code", signature = c("ANY"), function(code) paste(code, collapse = "\n")) setMethod(".preprocess_code", signature = c("language"), function(code) { - paste(vapply(lang2calls(code), deparse1, collapse = "\n", character(1L))) + paste( + vapply(lang2calls(code), deparse1, collapse = "\n", character(1L)), + collapse = "\n" + ) +}) +setMethod(".preprocess_code", signature = c("expression"), function(code) { + if (length(attr(code, "wholeSrcref")) == 0L) { + paste(lang2calls(code), collapse = "\n") + } else { + paste(attr(code, "wholeSrcref"), collapse = "\n") + } }) diff --git a/man/eval_code.Rd b/man/eval_code.Rd index eff9d6e2..347fe004 100644 --- a/man/eval_code.Rd +++ b/man/eval_code.Rd @@ -2,10 +2,7 @@ % Please edit documentation in R/qenv-eval_code.R, R/qenv-within.R \name{eval_code} \alias{eval_code} -\alias{eval_code,qenv,character-method} -\alias{eval_code,qenv,language-method} -\alias{eval_code,qenv,expression-method} -\alias{eval_code,qenv.error,ANY-method} +\alias{eval_code,qenv-method} \alias{within.qenv} \title{Evaluate code in \code{qenv}} \usage{ From 2eab2622eb1fe2b92cc67ee4eefdbbde014e0df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ka=C5=82=C4=99dkowski?= Date: Wed, 4 Jun 2025 10:42:44 +0200 Subject: [PATCH 07/15] Improvements (i hope so) (#254) Code balance is + because I've added one test ;] --- R/qenv-eval_code.R | 29 ++++++++++++++++------------ R/qenv-within.R | 17 ++++++---------- man/eval_code.Rd | 1 + tests/testthat/test-qenv_eval_code.R | 21 ++++++++++---------- tests/testthat/test-qenv_within.R | 5 +++++ 5 files changed, 39 insertions(+), 34 deletions(-) diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 91a89923..e53b2cf2 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -25,12 +25,17 @@ #' q <- eval_code(q, expression(assert_number(a))) #' #' @aliases eval_code,qenv-method +#' @aliases eval_code,qenv.error-method #' #' @export setGeneric("eval_code", function(object, code, cache = FALSE, ...) standardGeneric("eval_code")) setMethod("eval_code", signature = c(object = "qenv"), function(object, code, cache = FALSE, ...) { - code <- .preprocess_code(code) # preprocess code to ensure it is a character vector + if (!is.language(code) && !is.character(code)) { + stop("eval_code accepts code being language or character") + } + code <- .preprocess_code(code) + # preprocess code to ensure it is a character vector .eval_code(object = object, code = code, cache = cache, ...) }) @@ -38,6 +43,9 @@ setMethod("eval_code", signature = c(object = "qenv.error"), function(object, co #' @keywords internal .eval_code <- function(object, code, cache = FALSE, ...) { + if (identical(code, "")) { + return(object) + } parsed_code <- parse(text = code, keep.source = TRUE) object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(.GlobalEnv)) if (length(parsed_code) == 0) { @@ -100,17 +108,14 @@ setMethod("eval_code", signature = c(object = "qenv.error"), function(object, co } setGeneric(".preprocess_code", function(code) standardGeneric(".preprocess_code")) -setMethod(".preprocess_code", signature = c("ANY"), function(code) paste(code, collapse = "\n")) -setMethod(".preprocess_code", signature = c("language"), function(code) { - paste( - vapply(lang2calls(code), deparse1, collapse = "\n", character(1L)), - collapse = "\n" - ) -}) -setMethod(".preprocess_code", signature = c("expression"), function(code) { - if (length(attr(code, "wholeSrcref")) == 0L) { - paste(lang2calls(code), collapse = "\n") - } else { +setMethod(".preprocess_code", signature = c("character"), function(code) paste(code, collapse = "\n")) +setMethod(".preprocess_code", signature = c("ANY"), function(code) { + if (is.expression(code) && length(attr(code, "wholeSrcref"))) { paste(attr(code, "wholeSrcref"), collapse = "\n") + } else { + paste( + vapply(lang2calls(code), deparse1, collapse = "\n", character(1L)), + collapse = "\n" + ) } }) diff --git a/R/qenv-within.R b/R/qenv-within.R index ef68da14..33b8ebdc 100644 --- a/R/qenv-within.R +++ b/R/qenv-within.R @@ -48,20 +48,15 @@ #' @export #' within.qenv <- function(data, expr, ...) { - expr <- substitute(expr) + expr <- as.expression(substitute(expr)) extras <- list(...) - # Add braces for consistency. - if (!identical(as.list(expr)[[1L]], as.symbol("{"))) { - expr <- call("{", expr) - } - - calls <- as.list(expr)[-1] - # Inject extra values into expressions. - calls <- lapply(calls, function(x) do.call(substitute, list(x, env = extras))) - - eval_code(object = data, code = as.expression(calls)) + calls <- lapply(expr, function(x) do.call(substitute, list(x, env = extras))) + do.call( + eval_code, + utils::modifyList(extras, list(object = data, code = as.expression(calls))) + ) } diff --git a/man/eval_code.Rd b/man/eval_code.Rd index 347fe004..8c697e40 100644 --- a/man/eval_code.Rd +++ b/man/eval_code.Rd @@ -3,6 +3,7 @@ \name{eval_code} \alias{eval_code} \alias{eval_code,qenv-method} +\alias{eval_code,qenv.error-method} \alias{within.qenv} \title{Evaluate code in \code{qenv}} \usage{ diff --git a/tests/testthat/test-qenv_eval_code.R b/tests/testthat/test-qenv_eval_code.R index 689ee170..b16ddbdf 100644 --- a/tests/testthat/test-qenv_eval_code.R +++ b/tests/testthat/test-qenv_eval_code.R @@ -45,6 +45,11 @@ testthat::test_that("eval_code works with expression", { testthat::expect_equal(q1, list2env(list(a = 1, b = 2))) }) +testthat::test_that("eval_code ignores empty code", { + q <- qenv() + testthat::expect_identical(q, eval_code(q, "")) +}) + testthat::test_that("eval_code preserves original formatting when `srcref` is present in the expression", { code <- "# comment a <- 1L" @@ -77,12 +82,11 @@ testthat::test_that("eval_code works with quoted code block", { testthat::expect_equal(q1, list2env(list(a = 1, b = 2))) }) -testthat::test_that("eval_code fails with unquoted expression", { - b <- 3 - testthat::expect_error( - eval_code(qenv(), a <- b), - "unable to find an inherited method for function .eval_code. for signature" - ) +testthat::test_that("eval_code fails with code not being language nor character", { + msg <- "eval_code accepts code being language or character" + testthat::expect_error(eval_code(qenv(), NULL), msg) + testthat::expect_error(eval_code(qenv(), 1), msg) + testthat::expect_error(eval_code(qenv(), list()), msg) }) testthat::test_that("an error when calling eval_code returns a qenv.error object which has message and trace", { @@ -182,8 +186,3 @@ testthat::test_that("comments passed alone to eval_code that contain @linksto ta "x" ) }) - -testthat::test_that("Code executed with integer shorthand (1L) is the same as original", { - q <- within(qenv(), a <- 1L) - testthat::expect_identical(get_code(q), "a <- 1L") -}) diff --git a/tests/testthat/test-qenv_within.R b/tests/testthat/test-qenv_within.R index 9853b460..14a311a3 100644 --- a/tests/testthat/test-qenv_within.R +++ b/tests/testthat/test-qenv_within.R @@ -149,3 +149,8 @@ testthat::describe("within run with `=`", { testthat::expect_equal(q$i, 1) }) }) + +testthat::test_that("Code executed with integer shorthand (1L) is the same as original", { + q <- within(qenv(), a <- 1L) + testthat::expect_identical(get_code(q), "a <- 1L") +}) From 795997301b37136cff6bf9639e4038c500e45173 Mon Sep 17 00:00:00 2001 From: Marcin <133694481+m7pr@users.noreply.github.com> Date: Fri, 6 Jun 2025 14:26:46 +0200 Subject: [PATCH 08/15] Naming (#256) Companion to https://github.com/insightsengineering/teal.reporter/pull/334 Consequence of changing naming convention for `teal_report` object. --- R/qenv-eval_code.R | 16 ++++++++-------- R/qenv-within.R | 7 ++++--- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index e53b2cf2..7871fe52 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -9,7 +9,7 @@ #' @param code (`character`, `language` or `expression`) code to evaluate. #' It is possible to preserve original formatting of the `code` by providing a `character` or an #' `expression` being a result of `parse(keep.source = TRUE)`. -#' @param cache (`logical(1)`) whether to cache returned value of the code evaluation. +#' @param keep_output (`logical(1)`) whether to keep the output of the code evaluation. #' #' @param ... ([`dots`]) additional arguments passed to future methods. #' @@ -28,21 +28,21 @@ #' @aliases eval_code,qenv.error-method #' #' @export -setGeneric("eval_code", function(object, code, cache = FALSE, ...) standardGeneric("eval_code")) +setGeneric("eval_code", function(object, code, keep_output = FALSE, ...) standardGeneric("eval_code")) -setMethod("eval_code", signature = c(object = "qenv"), function(object, code, cache = FALSE, ...) { +setMethod("eval_code", signature = c(object = "qenv"), function(object, code, keep_output = FALSE, ...) { if (!is.language(code) && !is.character(code)) { stop("eval_code accepts code being language or character") } code <- .preprocess_code(code) # preprocess code to ensure it is a character vector - .eval_code(object = object, code = code, cache = cache, ...) + .eval_code(object = object, code = code, keep_output = keep_output, ...) }) -setMethod("eval_code", signature = c(object = "qenv.error"), function(object, code, cache = FALSE, ...) object) +setMethod("eval_code", signature = c(object = "qenv.error"), function(object, code, keep_output = FALSE, ...) object) #' @keywords internal -.eval_code <- function(object, code, cache = FALSE, ...) { +.eval_code <- function(object, code, keep_output = FALSE, ...) { if (identical(code, "")) { return(object) } @@ -64,8 +64,8 @@ setMethod("eval_code", signature = c(object = "qenv.error"), function(object, co tryCatch( { out <- eval(current_call, envir = object@.xData) - if (cache && i == length(code_split)) { - attr(current_code, "cache") <- out + if (keep_output && i == length(code_split)) { + attr(current_code, "output") <- out } if (!identical(parent.env(object@.xData), parent.env(.GlobalEnv))) { # needed to make sure that @.xData is always a sibling of .GlobalEnv diff --git a/R/qenv-within.R b/R/qenv-within.R index 33b8ebdc..398ca920 100644 --- a/R/qenv-within.R +++ b/R/qenv-within.R @@ -9,6 +9,7 @@ #' #' @param data (`qenv`) #' @param expr (`expression`) to evaluate. Must be inline code, see `Using language objects...` +#' @param keep_output (`logical(1)`) whether to keep the output of the code evaluation. #' @param ... named argument value will substitute a symbol in the `expr` matched by the name. #' For practical usage see Examples section below. #' @@ -47,7 +48,7 @@ #' #' @export #' -within.qenv <- function(data, expr, ...) { +within.qenv <- function(data, expr, keep_output = FALSE, ...) { expr <- as.expression(substitute(expr)) extras <- list(...) @@ -55,7 +56,7 @@ within.qenv <- function(data, expr, ...) { calls <- lapply(expr, function(x) do.call(substitute, list(x, env = extras))) do.call( eval_code, - utils::modifyList(extras, list(object = data, code = as.expression(calls))) + utils::modifyList(extras, list(object = data, code = as.expression(calls), keep_output = keep_output)) ) } @@ -63,6 +64,6 @@ within.qenv <- function(data, expr, ...) { #' @keywords internal #' #' @export -within.qenv.error <- function(data, expr, ...) { +within.qenv.error <- function(data, expr, keep_output = FALSE, ...) { data } From 5605340beef55b487a0156943a87bc61a37f725c Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 6 Jun 2025 12:29:34 +0000 Subject: [PATCH 09/15] [skip roxygen] [skip vbump] Roxygen Man Pages Auto Update --- man/eval_code.Rd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/man/eval_code.Rd b/man/eval_code.Rd index 8c697e40..37b972ef 100644 --- a/man/eval_code.Rd +++ b/man/eval_code.Rd @@ -7,9 +7,9 @@ \alias{within.qenv} \title{Evaluate code in \code{qenv}} \usage{ -eval_code(object, code, cache = FALSE, ...) +eval_code(object, code, keep_output = FALSE, ...) -\method{within}{qenv}(data, expr, ...) +\method{within}{qenv}(data, expr, keep_output = FALSE, ...) } \arguments{ \item{object}{(\code{qenv})} @@ -18,7 +18,7 @@ eval_code(object, code, cache = FALSE, ...) It is possible to preserve original formatting of the \code{code} by providing a \code{character} or an \code{expression} being a result of \code{parse(keep.source = TRUE)}.} -\item{cache}{(\code{logical(1)}) whether to cache returned value of the code evaluation.} +\item{keep_output}{(\code{logical(1)}) whether to keep the output of the code evaluation.} \item{...}{named argument value will substitute a symbol in the \code{expr} matched by the name. For practical usage see Examples section below.} From 3cd6246760897fb1a59e19b09937b90f8bdf1401 Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Mon, 9 Jun 2025 07:33:33 +0200 Subject: [PATCH 10/15] test of the keep_output --- tests/testthat/test-qenv_eval_code.R | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/testthat/test-qenv_eval_code.R b/tests/testthat/test-qenv_eval_code.R index b16ddbdf..26b598d1 100644 --- a/tests/testthat/test-qenv_eval_code.R +++ b/tests/testthat/test-qenv_eval_code.R @@ -186,3 +186,10 @@ testthat::test_that("comments passed alone to eval_code that contain @linksto ta "x" ) }) + +testthat::test_that("keep_output stores the last output of the `code` evaluation in its 'output' attribute", { + q <- eval_code(qenv(), "a <- 1L;b <-2L;c<- 3L", keep_output = TRUE) + testthat::expect_identical(attr(q@code[[1]], "output"), NULL) + testthat::expect_identical(attr(q@code[[2]], "output"), NULL) + testthat::expect_identical(attr(q@code[[3]], "output"), 3L) +}) From 7e1f07a2126e2efe6a00d07020f0d53129adf31d Mon Sep 17 00:00:00 2001 From: Dawid Kaledkowski Date: Mon, 9 Jun 2025 07:42:23 +0200 Subject: [PATCH 11/15] test within(keep_output) --- tests/testthat/test-qenv_within.R | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/testthat/test-qenv_within.R b/tests/testthat/test-qenv_within.R index 14a311a3..a92e0ff1 100644 --- a/tests/testthat/test-qenv_within.R +++ b/tests/testthat/test-qenv_within.R @@ -154,3 +154,17 @@ testthat::test_that("Code executed with integer shorthand (1L) is the same as or q <- within(qenv(), a <- 1L) testthat::expect_identical(get_code(q), "a <- 1L") }) + +testthat::test_that("keep_output stores the last output of the `code` evaluation in its 'output' attribute", { + q <- within(qenv(), + { + a <- 1L + b <- 2L + c <- 3L + }, + keep_output = TRUE + ) + testthat::expect_identical(attr(q@code[[1]], "output"), NULL) + testthat::expect_identical(attr(q@code[[2]], "output"), NULL) + testthat::expect_identical(attr(q@code[[3]], "output"), 3L) +}) From 011071688358bcf5496514733150a865d66b8cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 12 Jun 2025 00:36:40 +0100 Subject: [PATCH 12/15] cleanup: remove keep_output --- R/qenv-eval_code.R | 17 ++++++----------- R/qenv-within.R | 7 +++---- man/eval_code.Rd | 6 ++---- tests/testthat/test-qenv_eval_code.R | 7 ------- tests/testthat/test-qenv_within.R | 14 -------------- 5 files changed, 11 insertions(+), 40 deletions(-) diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 7871fe52..81f882b4 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -9,8 +9,6 @@ #' @param code (`character`, `language` or `expression`) code to evaluate. #' It is possible to preserve original formatting of the `code` by providing a `character` or an #' `expression` being a result of `parse(keep.source = TRUE)`. -#' @param keep_output (`logical(1)`) whether to keep the output of the code evaluation. -#' #' @param ... ([`dots`]) additional arguments passed to future methods. #' #' @return @@ -28,21 +26,21 @@ #' @aliases eval_code,qenv.error-method #' #' @export -setGeneric("eval_code", function(object, code, keep_output = FALSE, ...) standardGeneric("eval_code")) +setGeneric("eval_code", function(object, code, ...) standardGeneric("eval_code")) -setMethod("eval_code", signature = c(object = "qenv"), function(object, code, keep_output = FALSE, ...) { +setMethod("eval_code", signature = c(object = "qenv"), function(object, code, ...) { if (!is.language(code) && !is.character(code)) { stop("eval_code accepts code being language or character") } code <- .preprocess_code(code) # preprocess code to ensure it is a character vector - .eval_code(object = object, code = code, keep_output = keep_output, ...) + .eval_code(object = object, code = code, ...) }) -setMethod("eval_code", signature = c(object = "qenv.error"), function(object, code, keep_output = FALSE, ...) object) +setMethod("eval_code", signature = c(object = "qenv.error"), function(object, code, ...) object) #' @keywords internal -.eval_code <- function(object, code, keep_output = FALSE, ...) { +.eval_code <- function(object, code, ...) { if (identical(code, "")) { return(object) } @@ -63,10 +61,7 @@ setMethod("eval_code", signature = c(object = "qenv.error"), function(object, co x <- withCallingHandlers( tryCatch( { - out <- eval(current_call, envir = object@.xData) - if (keep_output && i == length(code_split)) { - attr(current_code, "output") <- out - } + eval(current_call, envir = object@.xData) if (!identical(parent.env(object@.xData), parent.env(.GlobalEnv))) { # needed to make sure that @.xData is always a sibling of .GlobalEnv # could be changed when any new package is added to search path (through library or require call) diff --git a/R/qenv-within.R b/R/qenv-within.R index 398ca920..33b8ebdc 100644 --- a/R/qenv-within.R +++ b/R/qenv-within.R @@ -9,7 +9,6 @@ #' #' @param data (`qenv`) #' @param expr (`expression`) to evaluate. Must be inline code, see `Using language objects...` -#' @param keep_output (`logical(1)`) whether to keep the output of the code evaluation. #' @param ... named argument value will substitute a symbol in the `expr` matched by the name. #' For practical usage see Examples section below. #' @@ -48,7 +47,7 @@ #' #' @export #' -within.qenv <- function(data, expr, keep_output = FALSE, ...) { +within.qenv <- function(data, expr, ...) { expr <- as.expression(substitute(expr)) extras <- list(...) @@ -56,7 +55,7 @@ within.qenv <- function(data, expr, keep_output = FALSE, ...) { calls <- lapply(expr, function(x) do.call(substitute, list(x, env = extras))) do.call( eval_code, - utils::modifyList(extras, list(object = data, code = as.expression(calls), keep_output = keep_output)) + utils::modifyList(extras, list(object = data, code = as.expression(calls))) ) } @@ -64,6 +63,6 @@ within.qenv <- function(data, expr, keep_output = FALSE, ...) { #' @keywords internal #' #' @export -within.qenv.error <- function(data, expr, keep_output = FALSE, ...) { +within.qenv.error <- function(data, expr, ...) { data } diff --git a/man/eval_code.Rd b/man/eval_code.Rd index 37b972ef..a6a1875b 100644 --- a/man/eval_code.Rd +++ b/man/eval_code.Rd @@ -7,9 +7,9 @@ \alias{within.qenv} \title{Evaluate code in \code{qenv}} \usage{ -eval_code(object, code, keep_output = FALSE, ...) +eval_code(object, code, ...) -\method{within}{qenv}(data, expr, keep_output = FALSE, ...) +\method{within}{qenv}(data, expr, ...) } \arguments{ \item{object}{(\code{qenv})} @@ -18,8 +18,6 @@ eval_code(object, code, keep_output = FALSE, ...) It is possible to preserve original formatting of the \code{code} by providing a \code{character} or an \code{expression} being a result of \code{parse(keep.source = TRUE)}.} -\item{keep_output}{(\code{logical(1)}) whether to keep the output of the code evaluation.} - \item{...}{named argument value will substitute a symbol in the \code{expr} matched by the name. For practical usage see Examples section below.} diff --git a/tests/testthat/test-qenv_eval_code.R b/tests/testthat/test-qenv_eval_code.R index 26b598d1..b16ddbdf 100644 --- a/tests/testthat/test-qenv_eval_code.R +++ b/tests/testthat/test-qenv_eval_code.R @@ -186,10 +186,3 @@ testthat::test_that("comments passed alone to eval_code that contain @linksto ta "x" ) }) - -testthat::test_that("keep_output stores the last output of the `code` evaluation in its 'output' attribute", { - q <- eval_code(qenv(), "a <- 1L;b <-2L;c<- 3L", keep_output = TRUE) - testthat::expect_identical(attr(q@code[[1]], "output"), NULL) - testthat::expect_identical(attr(q@code[[2]], "output"), NULL) - testthat::expect_identical(attr(q@code[[3]], "output"), 3L) -}) diff --git a/tests/testthat/test-qenv_within.R b/tests/testthat/test-qenv_within.R index a92e0ff1..14a311a3 100644 --- a/tests/testthat/test-qenv_within.R +++ b/tests/testthat/test-qenv_within.R @@ -154,17 +154,3 @@ testthat::test_that("Code executed with integer shorthand (1L) is the same as or q <- within(qenv(), a <- 1L) testthat::expect_identical(get_code(q), "a <- 1L") }) - -testthat::test_that("keep_output stores the last output of the `code` evaluation in its 'output' attribute", { - q <- within(qenv(), - { - a <- 1L - b <- 2L - c <- 3L - }, - keep_output = TRUE - ) - testthat::expect_identical(attr(q@code[[1]], "output"), NULL) - testthat::expect_identical(attr(q@code[[2]], "output"), NULL) - testthat::expect_identical(attr(q@code[[3]], "output"), 3L) -}) From f08c90b4605702754d4f3e1892dc318f9c17bddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 12 Jun 2025 10:08:25 +0100 Subject: [PATCH 13/15] docs: update documentation --- R/qenv-eval_code.R | 2 +- R/qenv-within.R | 3 +-- man/eval_code.Rd | 55 ++++------------------------------------ man/within.qenv.Rd | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 53 deletions(-) create mode 100644 man/within.qenv.Rd diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 81f882b4..2fd434c4 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -24,7 +24,7 @@ #' #' @aliases eval_code,qenv-method #' @aliases eval_code,qenv.error-method -#' +#' @seealso [within.qenv] #' @export setGeneric("eval_code", function(object, code, ...) standardGeneric("eval_code")) diff --git a/R/qenv-within.R b/R/qenv-within.R index 33b8ebdc..951c2273 100644 --- a/R/qenv-within.R +++ b/R/qenv-within.R @@ -1,3 +1,4 @@ +#' Evaluate code in `qenv` #' @details #' `within()` is a convenience method that wraps `eval_code` to provide a simplified way of passing expression. #' `within` accepts only inline expressions (both simple and compound) and allows to substitute `expr` @@ -43,8 +44,6 @@ #' within(q, exprlist) # fails #' do.call(within, list(q, do.call(c, exprlist))) #' -#' @rdname eval_code -#' #' @export #' within.qenv <- function(data, expr, ...) { diff --git a/man/eval_code.Rd b/man/eval_code.Rd index a6a1875b..2cece165 100644 --- a/man/eval_code.Rd +++ b/man/eval_code.Rd @@ -1,15 +1,12 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/qenv-eval_code.R, R/qenv-within.R +% Please edit documentation in R/qenv-eval_code.R \name{eval_code} \alias{eval_code} \alias{eval_code,qenv-method} \alias{eval_code,qenv.error-method} -\alias{within.qenv} \title{Evaluate code in \code{qenv}} \usage{ eval_code(object, code, ...) - -\method{within}{qenv}(data, expr, ...) } \arguments{ \item{object}{(\code{qenv})} @@ -18,12 +15,7 @@ eval_code(object, code, ...) It is possible to preserve original formatting of the \code{code} by providing a \code{character} or an \code{expression} being a result of \code{parse(keep.source = TRUE)}.} -\item{...}{named argument value will substitute a symbol in the \code{expr} matched by the name. -For practical usage see Examples section below.} - -\item{data}{(\code{qenv})} - -\item{expr}{(\code{expression}) to evaluate. Must be inline code, see \verb{Using language objects...}} +\item{...}{(\code{\link{dots}}) additional arguments passed to future methods.} } \value{ \code{qenv} environment with \code{code/expr} evaluated or \code{qenv.error} if evaluation fails. @@ -34,17 +26,7 @@ Evaluate code in \code{qenv} \details{ \code{eval_code()} evaluates given code in the \code{qenv} environment and appends it to the \code{code} slot. Thus, if the \code{qenv} had been instantiated empty, contents of the environment are always a result of the stored code. - -\code{within()} is a convenience method that wraps \code{eval_code} to provide a simplified way of passing expression. -\code{within} accepts only inline expressions (both simple and compound) and allows to substitute \code{expr} -with \code{...} named argument values. } -\section{Using language objects with \code{within}}{ - -Passing language objects to \code{expr} is generally not intended but can be achieved with \code{do.call}. -Only single \code{expression}s will work and substitution is not available. See examples. -} - \examples{ # evaluate code in qenv q <- qenv() @@ -53,34 +35,7 @@ q <- eval_code(q, "b <- 2L # with comment") q <- eval_code(q, quote(library(checkmate))) q <- eval_code(q, expression(assert_number(a))) -# evaluate code using within -q <- qenv() -q <- within(q, { - i <- iris -}) -q <- within(q, { - m <- mtcars - f <- faithful -}) -q -get_code(q) - -# inject values into code -q <- qenv() -q <- within(q, i <- iris) -within(q, print(dim(subset(i, Species == "virginica")))) -within(q, print(dim(subset(i, Species == species)))) # fails -within(q, print(dim(subset(i, Species == species))), species = "versicolor") -species_external <- "versicolor" -within(q, print(dim(subset(i, Species == species))), species = species_external) - -# pass language objects -expr <- expression(i <- iris, m <- mtcars) -within(q, expr) # fails -do.call(within, list(q, expr)) - -exprlist <- list(expression(i <- iris), expression(m <- mtcars)) -within(q, exprlist) # fails -do.call(within, list(q, do.call(c, exprlist))) - +} +\seealso{ +\link{within.qenv} } diff --git a/man/within.qenv.Rd b/man/within.qenv.Rd new file mode 100644 index 00000000..a4f2237c --- /dev/null +++ b/man/within.qenv.Rd @@ -0,0 +1,62 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/qenv-within.R +\name{within.qenv} +\alias{within.qenv} +\title{Evaluate code in \code{qenv}} +\usage{ +\method{within}{qenv}(data, expr, ...) +} +\arguments{ +\item{data}{(\code{qenv})} + +\item{expr}{(\code{expression}) to evaluate. Must be inline code, see \verb{Using language objects...}} + +\item{...}{named argument value will substitute a symbol in the \code{expr} matched by the name. +For practical usage see Examples section below.} +} +\description{ +Evaluate code in \code{qenv} +} +\details{ +\code{within()} is a convenience method that wraps \code{eval_code} to provide a simplified way of passing expression. +\code{within} accepts only inline expressions (both simple and compound) and allows to substitute \code{expr} +with \code{...} named argument values. +} +\section{Using language objects with \code{within}}{ + +Passing language objects to \code{expr} is generally not intended but can be achieved with \code{do.call}. +Only single \code{expression}s will work and substitution is not available. See examples. +} + +\examples{ +# evaluate code using within +q <- qenv() +q <- within(q, { + i <- iris +}) +q <- within(q, { + m <- mtcars + f <- faithful +}) +q +get_code(q) + +# inject values into code +q <- qenv() +q <- within(q, i <- iris) +within(q, print(dim(subset(i, Species == "virginica")))) +within(q, print(dim(subset(i, Species == species)))) # fails +within(q, print(dim(subset(i, Species == species))), species = "versicolor") +species_external <- "versicolor" +within(q, print(dim(subset(i, Species == species))), species = species_external) + +# pass language objects +expr <- expression(i <- iris, m <- mtcars) +within(q, expr) # fails +do.call(within, list(q, expr)) + +exprlist <- list(expression(i <- iris), expression(m <- mtcars)) +within(q, exprlist) # fails +do.call(within, list(q, do.call(c, exprlist))) + +} From 257bca2cc86f113937148bbc2ee1c49ee6ae472e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:10:32 +0100 Subject: [PATCH 14/15] docs: update news --- NEWS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS.md b/NEWS.md index 038c4dc3..fa6d45a5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,10 @@ * Fix a problem detecting co-occurrences when expression has multiple lines. +### Miscellaneous + +* Refactor `eval_code` method signature to allow for more flexibility when extending the `eval_code`/`within` functions. + # teal.code 0.6.1 ### Bug fixes From c4bf214eff30e145b56a74a1c3d09b17dcfaea00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ver=C3=ADssimo?= <211358+averissimo@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:22:47 +0100 Subject: [PATCH 15/15] chore: add extra early return statement in eval_code for empty code --- R/qenv-eval_code.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 2fd434c4..878f5eca 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -41,7 +41,7 @@ setMethod("eval_code", signature = c(object = "qenv.error"), function(object, co #' @keywords internal .eval_code <- function(object, code, ...) { - if (identical(code, "")) { + if (identical(trimws(code), "") || length(code) == 0) { return(object) } parsed_code <- parse(text = code, keep.source = TRUE)