Search code examples
rmetaprogrammingstatic-code-analysis

Replace one symbol in an expression with multiple values


Given an arbitrary fixed expression, I want to substitute a single symbol with a collection of multiple values. Examples:

Expression       | Symbol | Replace with               | Desired Output
-----------------------------------------------------------------------------------------
f(x, 5)          | x      | a = 1, b = sym, c = "char" | f(a = 1, b = sym, c = "char", 5)
g(f(h(y)), z)    | y      | 1, 2, 3                    | g(f(h(1, 2, 3)), z)
g(f(h(y), z), z) | z      | 4, x                       | g(f(h(y), 4, x), 4, x)

The substitute() function comes close, but it is not exactly what I am looking for. In the example below, I want to turn f(x) into f(1, b = 4, c = d), but I have not yet found the right env argument.

substitute(
  expr = f(x),
  env = list(x = list(1, b = 4, c = rlang::sym("d")))
)
#> f(list(1, b = 4, c = d))

substitute(
  expr = f(x),
  env = list(x = as.call(c(quote(x), 1, b = 4, c = quote(d)))[-1])
)
#> f(1(b = 4, c = d))

Created on 2019-02-09 by the reprex package (v0.2.1)

Is it possible to find an env such that substitute(f(x), env) equals f(1, b = 4, c = d)?

Remarks:


Solution

  • Here's a take on a splicing function

    splice <- function(x, replacements) {
      if (is(x, "call")) {
        as.call(do.call("c",lapply(as.list(x), splice, replacements), quote=T))
      } else if (is(x, "name")) {
        if (deparse(x) %in% names(replacements)) {
          return(replacements[[deparse(x)]])
        } else {
          list(x)
        }
      } else {
        list(x)
      }
    }
    

    It seems to work with the sample input

    splice(quote(f(x, 5) ), list(x=list(a = 1, b = quote(sym), c = "char" )))
    # f(a = 1, b = sym, c = "char", 5)
    splice(quote(g(f(h(y)), z)) , list(y=list(1,2,3)))
    # g(f(h(1, 2, 3)), z)
    splice(quote(g(f(h(y), z), z)), list(z=list(4, quote(x))) )
    # g(f(h(y), 4, x), 4, x)
    

    Basically you just swap out the symbol names. it should also work with single variable replacements that aren't in a list.

    splice(quote(f(x,5)), list(x=7))
    # f(7, 5)
    

    You basically need to re-write the call by manipulating it as a list. This is what the tidyverse functions are doing behind the scene. They intercept the current call, re-write it, then evaluate the newly expanded call. substitute will never work because you aren't just replacing one symbol with one value. You need to change the number of parameters you are passing to a function.