3333# ' environment, and combines them with default values from the parent
3434# ' function’s formal arguments. Unevaluated expressions (e.g., `a + b`) are
3535# ' preserved as character strings via `deparse()`, while scalars (including
36- # ' symbols like `i` in loops that evaluate to scalars) and complex objects
37- # ' (e.g., `lm`, `SpatRaster`) are handled appropriately:
36+ # ' symbols like `i` in loops that evaluate to scalars), multi-element vectors
37+ # ' (e.g., `c(1, 2)`), and complex objects (e.g., `lm`, `SpatRaster`) are
38+ # ' handled appropriately:
3839# ' - Symbols (e.g., `i` in `lapply`) are treated as matching their evaluated
3940# ' scalar values, resulting in a single column.
4041# ' - Calls (e.g., `a + b`) result in `_orig`/`_eval` pairs.
42+ # ' - Multi-element vectors (e.g., `c(1, 2)`) result in a single column when
43+ # ' unevaluated and evaluated forms match, or `_orig`/`_eval` pairs otherwise.
4144# ' - Complex objects are wrapped in lists in `_eval` columns.
4245# ' Columns are ordered based on the original argument sequence: single columns
4346# ' (for matching values) appear first, followed by `_orig` and `_eval` pairs
6770# ' a <- 5
6871# ' b <- 3
6972# '
70- # ' Function1 <- function(w = 5, x, y, z = 10 ) {
73+ # ' Function1 <- function(w = 5, x, y, z = c(1, 2) ) {
7174# ' Args <- IASDT.R::RecordArgs(call = match.call(), env = parent.frame())
7275# ' return(Args)
7376# ' }
7477# '
75- # ' # --------------------------------------------------------------
76- # '
7778# ' # Basic usage with scalars and expressions
7879# ' Out1 <- Function1(x = a + b, y = 2)
7980# '
8384# ' Out1$x_orig # "a + b" (unevaluated expression)
8485# ' Out1$x_eval # 8 (evaluated result)
8586# ' Out1$y # 2 (single column, scalar matches evaluated)
86- # ' Out1$z # 10 (single column, default matches evaluated)
87+ # ' Out1$z_orig # "c(1, 2)"
88+ # ' Out1$z_eval # c(1, 2) (single column, default matches evaluated)
8789# '
8890# ' # --------------------------------------------------------------
8991# '
@@ -134,18 +136,18 @@ RecordArgs <- function(ExportPath = NULL, call = NULL, env = NULL) {
134136
135137 # Capture the parent function's call: use provided call (e.g., from
136138 # match.call()) or fall back to sys.call(-1) for direct calls
137- call_info <- if (! is.null(call )) call else sys.call(- 1 )
139+ call_info <- if (! is.null(call )) {
140+ call
141+ } else {
142+ sys.call(- 1 )
143+ }
138144
139145 # Check if call_info is valid; stop if not called within a function
140146 if (is.null(call_info )) {
141147 stop(
142148 " RecordArgs() must be called from within another function" , call. = FALSE )
143149 }
144150
145- # Extract the arguments from the call, excluding the function name (first
146- # element)
147- args_list <- as.list(call_info )[- 1 ]
148-
149151 # Get the name of the calling function as a character string
150152 calling_func <- deparse(call_info [[1 ]])
151153
@@ -157,8 +159,20 @@ RecordArgs <- function(ExportPath = NULL, call = NULL, env = NULL) {
157159 parent_func <- sys.function(- 1 )
158160 formals_full <- formals(parent_func )
159161
160- # Evaluate the captured arguments in the parent environment
161- args_values <- lapply(args_list , eval , envir = parent_env )
162+ # Extract the arguments from the call, excluding the function name (first
163+ # element), and preserve defaults for missing arguments
164+ args_list <- as.list(call_info )[- 1 ]
165+ args_list <- utils :: modifyList(formals_full , args_list )
166+
167+ # Evaluate the captured arguments in the parent environment, handling all
168+ # cases safely
169+ args_values <- lapply(args_list , function (x ) {
170+ tryCatch(eval(x , envir = parent_env ), error = function (e ) {
171+ # If evaluation fails in parent_env, try globalenv() to resolve globals
172+ # (e.g., 'a' and 'b' in 'a + b')
173+ tryCatch(eval(x , envir = globalenv()), error = function (e2 ) NA )
174+ })
175+ })
162176
163177 # Name the evaluated values with their corresponding argument names
164178 recorded_values <- stats :: setNames(args_values , names(args_list ))
@@ -207,14 +221,28 @@ RecordArgs <- function(ExportPath = NULL, call = NULL, env = NULL) {
207221 # e.g., "x_orig" (unevaluated forms)
208222 uneval_cols <- paste0(diff_cols , " _orig" )
209223
210- # Format evaluated values: scalars as-is, complex objects (e.g., SpatRaster)
211- # wrapped in lists
224+ # Format evaluated values: scalars as-is (e.g., 5, 8, NA), multi-element
225+ # vectors (e.g., c(1, 2)) and complex objects in lists
212226 eval_values <- purrr :: map(
213227 .x = as.list(Evaluated ),
214228 .f = function (x ) {
215- if (is.vector(x ) && length(x ) == 1 && ! is.list(x )) {
216- # Scalars remain as-is (e.g., 8, "BCD")
217- return (x )
229+ if (is.vector(x ) && ! is.list(x )) {
230+ if (length(x ) == 1 ) {
231+ # Scalars (e.g., 5, 8, NA) remain as-is
232+ return (x )
233+ } else {
234+ # Multi-element vectors (e.g., c(1, 2)) wrapped in a list
235+ return (list (x ))
236+ }
237+ } else if (is.language(x )) {
238+ # Evaluate language objects and handle based on length
239+ eval_result <- eval(x , envir = parent_env )
240+ if (is.vector(eval_result ) && ! is.list(eval_result ) &&
241+ length(eval_result ) == 1 ) {
242+ return (eval_result )
243+ } else {
244+ return (list (eval_result ))
245+ }
218246 } else {
219247 if (inherits(x , " SpatRaster" )) {
220248 # Wrap SpatRaster objects for storage
@@ -225,17 +253,22 @@ RecordArgs <- function(ExportPath = NULL, call = NULL, env = NULL) {
225253 }
226254 })
227255
228- # Format unevaluated values: calls as character strings, scalars as-is,
229- # complex objects in lists
256+ # Format unevaluated values: calls as strings (e.g., "a + b"), scalars as-is
257+ # (e.g., 5), multi-element vectors (e.g., c(1, 2)) in lists
230258 uneval_values <- purrr :: map(
231259 .x = as.list(Unevaluated ),
232260 .f = function (x ) {
233261 if (is.call(x )) {
234262 # Convert calls (e.g., a + b) to strings without quotes
235263 return (noquote(deparse(x )))
236- } else if (is.vector(x ) && length(x ) == 1 && ! is.list(x )) {
237- # Scalars (e.g., 2, 10) remain as-is
238- return (x )
264+ } else if (is.vector(x ) && ! is.list(x )) {
265+ if (length(x ) == 1 ) {
266+ # Scalars (e.g., 5) remain as-is
267+ return (x )
268+ } else {
269+ # Multi-element vectors (e.g., c(1, 2)) wrapped in a list
270+ return (list (x ))
271+ }
239272 } else {
240273 if (inherits(x , " SpatRaster" )) {
241274 # Wrap SpatRaster objects
0 commit comments