Like any parse tree, R expressions are structured as trees of nodes. Each node has two components: the head and the tail (though technically there is actually a third component for argument names, see details). Due to R's lisp roots, the head of a node (or cons cell) is called the CAR and the tail is called the CDR (pronounced car and cou-der). While R's ordinary subsetting operators have builtin support for indexing into these trees and replacing elements, it is sometimes useful to manipulate the nodes more directly. This is the purpose of functions like node_car() and mut_node_car(). They are particularly useful to prototype algorithms for your C-level functions.

  • node_car() and mut_node_car() access or change the head of a node.

  • node_cdr() and mut_node_cdr() access or change the tail of a node.

  • Variants like node_caar() or mut_node_cdar() deal with the CAR of the CAR of a node or the CDR of the CAR of a node respectively. The letters in the middle indicate the type (CAR or CDR) and order of access.

  • node_tag() and mut_node_tag() access or change the tag of a node. This is meant for argument names and should only contain symbols (not strings).

  • node() creates a new node from two components.

node(newcar, newcdr)

node_car(x)

node_cdr(x)

node_caar(x)

node_cadr(x)

node_cdar(x)

node_cddr(x)

mut_node_car(x, newcar)

mut_node_cdr(x, newcdr)

mut_node_caar(x)

mut_node_cadr(x, newcar)

mut_node_cdar(x)

mut_node_cddr(x, newcdr)

node_tag(x)

mut_node_tag(x, newtag)

Arguments

newcar, newcdr

The new CAR or CDR for the node. These can be any R objects.

x

A language or pairlist node. Note that these functions are barebones and do not perform any type checking.

newtag

The new tag for the node. This should be a symbol.

Details

R has two types of nodes to represent parse trees: language nodes, which represent function calls, and pairlist nodes, which represent arguments in a function call. These are the exact same data structures with a different name. This distinction is helpful for parsing the tree: the top-level node of a function call always has language type while its arguments have pairlist type.

Note that it is risky to manipulate calls at the node level. First, the calls are changed inplace. This is unlike base R operators which create a new copy of the language tree for each modification. To make sure modifying a language object does not produce side-effects, rlang exports the duplicate() function to create deep copy (or optionally a shallow copy, i.e. only the top-level node is copied). The second danger is that R expects language trees to be structured as a NULL-terminated list. The CAR of a node is a data slot and can contain anything, including another node (which is how you form trees, as opposed to mere linked lists). On the other hand, the CDR has to be either another node, or NULL. If it is terminated by anything other than the NULL object, many R commands will crash, including functions like str(). It is up to you to ensure that the language list you have modified is NULL-terminated.

Finally, all nodes can contain metadata in the TAG slot. This is meant for argument names and R expects tags to contain a symbol (not a string).

See also

duplicate() for creating copy-safe objects, lang_head() and lang_tail() as slightly higher level alternatives that check their input, and base::pairlist() for an easier way of creating a linked list of nodes.

Examples

# Changing a node component happens in place and can have side # effects. Let's create a language object and a copy of it: lang <- quote(foo(bar)) copy <- lang # Using R's builtin operators to change the language tree does not # create side effects: copy[[2]] <- quote(baz) copy
#> foo(baz)
lang
#> foo(bar)
# On the other hand, the CAR and CDR operators operate in-place. Let's # create new objects since the previous examples triggered a copy: lang <- quote(foo(bar)) copy <- lang # Now we change the argument pairlist of `copy`, making sure the new # arguments are NULL-terminated: mut_node_cdr(copy, node(quote(BAZ), NULL))
#> foo(BAZ)
# Or equivalently: mut_node_cdr(copy, pairlist(quote(BAZ)))
#> foo(BAZ)
copy
#> foo(BAZ)
# The original object has been changed in place: lang
#> foo(BAZ)