seandavi
seandavi

Reputation: 2968

How can I parse and modify an R expression, possibly using non-standard evaluation?

I'd like to parse an R expression into a list and optionally modify aspects of it before finally turning it into a json object. As an example, I'm trying to get to something like:

{"op": "=",
      "content": {
          "lhs": "gender",
          "rhs": ["male"]
      }
}

I would be starting with an R expression like:

gender == "male"

I can use pryr::ast to get a text version of the tree, but I'd like to get this as a list like:

op: "=="
  [[1]]: "gender"
  [[2]]: "male"

The details of the "format" of the list are not so important, just to be clear. I am simply aiming to get a computable and modifiable parse tree of R expressions.

Upvotes: 1

Views: 257

Answers (2)

IRTFM
IRTFM

Reputation: 263451

Here's an approach for the output portion of your request, using modifications of the methods cited in my comment. This is based on Hadley's pkg:pryr. See ?Ops for the list of infix operators. I've seen functions lhs and rhs defined ... IIRC in Hadley's Advanced Programming text. Obviously the only functions labeled as 'ops' will be the infix math and logical, but a more complete labeling of Math(), Complex(), and Summary() functions could be done with the other lists in the ?groupGeneric page:

call_tree2(quote(gender == "male")) # relabeling of items in pryr-functions
#--------
 - call:
   - `op: ==
   - `gender
   -  "male" 

Functions defined below:

library(pryr) # also loads the stringr namespace
# although the `tree` function is not exported, you can see it with:
pryr:::tree   # now for some hacking and adding of logic
tree2<-
function (x, level = 1, width = getOption("width"), branch = " - ") 
{
    indent <- str_c(str_dup("  ", level - 1), branch)
    if (is.atomic(x) && length(x) == 1) {
        label <- paste0(" ", deparse(x)[1])
        children <- NULL
    }
    else if (is.name(x)) {
        x <- as.character(x)
        if (x == "") {
            label <- "`MISSING"
        }
        if (x %in% c("+", "-", "*", "/", "^", "%%", "%/%",
"&", "|", "!","==", "!=", "<", "<=", ">=", ">") ) {
             label <- paste0("`op: ", as.character(x))}
        else {
            label <- paste0("`", as.character(x))
        }
        children <- NULL
    }
    else if (is.call(x)) {
        label <- "call:"
        children <- vapply(as.list(x), tree2, character(1), level = level + 
            1, width = width - 3)
    }
    else if (is.pairlist(x)) {
        label <- "[]"
        branches <- paste("", format(names(x)), "=")
        children <- character(length(x))
        for (i in seq_along(x)) {
            children[i] <- tree2(x[[i]], level = level + 1, width = width - 
                3, branch = branches[i])
        }
    }
    else {
        if (inherits(x, "srcref")) {
            label <- "<srcref>"
        }
        else {
            label <- paste0("", typeof(x), "")
        }
        children <- NULL
    }
    label <- str_trunc(label, width - 3)
    if (is.null(children)) {
        paste0(indent, label)
    }
    else {
        paste0(indent, label, "\n", paste0(children, collapse = "\n"))
    }
}
environment(tree2)<-environment(pryr:::tree)

And now call it with call_tree2:

pryr::call_tree
call_tree2 <- 
function (x, width = getOption("width")) 
{
    if (is.expression(x) || is.list(x)) {
        trees <- vapply(x, tree2, character(1), width = width)
        out <- str_c(trees, collapse = "\n\n")
    }
    else {
        out <- tree2(x, width = width)
    }
    cat(out, "\n")
}
environment(call_tree2)<-environment(pryr::call_tree)

Upvotes: 2

Hong Ooi
Hong Ooi

Reputation: 57696

Is something like this what you're looking for?

expr <- quote(gender == "male")

expr[[1]]
# `==`
expr[[2]]
# gender
expr[[3]]
# "male"
expr[[3]] <- "female"
expr
# gender == "female"

Upvotes: 5

Related Questions