I am reading in some code and need to determine the parameter names of the arguments that were passed. If the person named the parameters, I can find them with x[[param_name]]
. When it isn't explicit, how do I find them?
In this example, I am trying to find the "from" parameter. I can see that it is index #4 but it could be in position #2 or elsewhere and any or all of them could be unnamed.
Thank you for your time.
x <- parse(text = "seq.int(to = 10, by = 2, 0)")
code_as_call <- as.call(x)[[1]]
View(code_as_call)
param_to <- code_as_call[["to"]]
param_by <- code_as_call[["by"]]
param_from <- "???" # how do I find this?
With help from @MichaelChirico, this is the solution I came up with. I had to consider partial matching as an option.
arg_names <- function(x) {
x_fn <- x[[1]][[3]]
default_args <- c("", formalArgs(args(eval(x_fn[[1]]))))
missing_names <- is.null(names(x_fn))
seq_args <- seq_along(x_fn)
#skip_last <- head(seq_args, -1)
if (missing_names) {
# assign names after first position
names(x_fn) <- default_args[seq_args]
} else {
orig_args <- names(x_fn)
has_name <- nchar(orig_args) > 0
# line up args including partial matches
explicit_args <- pmatch(orig_args[has_name], default_args)
# update names
names(x_fn)[which(has_name)] <- default_args[explicit_args]
updated_args <- names(x_fn)
# missing args
avail_args <- setdiff(default_args, updated_args[has_name])
missing_name <- which(!has_name)
implicit_args <- avail_args[seq_along(missing_name)]
# update names
names(x_fn)[missing_name] <- implicit_args
}
names(x_fn)
}
arg_names(expression(new_string <- gsub(' ', '_', 'a b c')))
#> [1] "" "pattern" "replacement" "x"
arg_names(expression(new_string <- gsub(x = 'a b c', ' ', '_')))
#> [1] "" "x" "pattern" "replacement"
arg_names(expression(new_string <- gsub(x = 'a b c', pat = ' ', rep = '_')))
#> [1] "" "x" "pattern" "replacement"
Created on 2020-07-26 by the reprex package (v0.3.0)
This is not a fully general solution but hopefully illustrates all the necessary base
helper metaprogramming functions you'll need to get there.
We first have to get the names of the available arguments:
xi = x[[1L]]
all_args = setdiff(formalArgs(args(eval(xi[[1L]]))), '...')
all_args
# [1] "from" "to" "by" "length.out" "along.with"
NB: often, we can just use formalArgs
directly, but as seq.int
is a Primitive
function (is.primitive(seq.int)
), formals
returns NULL
-- see ?formals
which recommends formals(args(.))
in this case.
Then, we have to see what was used in the actual call:
used_args = names(xi[-1L])
# [1] "to" "by" ""
Then, we take arguments in order from all_args
among those that aren't named:
avail_args = setdiff(all_args, used_args[has_name])
implicit_args = avail_args[seq_along(used_args[!has_name])]
implicit_args
# [1] from