R/node.R

Defines functions new_call node_list_reverse node_append node_walk_last node_walk_nonnull node_walk node_tree_clone is_node_list is_node is_pairlist node_poke_tag node_tag node_get_car node_get node_poke_cddr node_poke_cdar node_poke_cadr node_poke_caar node_poke_cdr node_poke_car node_cddr node_cdar node_cadr node_caar node_cdr node_car new_node

Documented in is_node is_node_list is_pairlist new_call new_node node_caar node_cadr node_car node_cdar node_cddr node_cdr node_poke_caar node_poke_cadr node_poke_car node_poke_cdar node_poke_cddr node_poke_cdr node_poke_tag node_tag

#' Helpers for pairlist and language nodes
#'
#' @description
#'
#' **Important**: These functions are for expert R programmers only.
#' You should only use them if you feel comfortable manipulating low
#' level R data structures at the C level. We export them at the R level
#' in order to make it easy to prototype C code. They don't perform
#' any type checking and can crash R very easily (try to take the CAR
#' of an integer vector --- save any important objects beforehand!).
#'
#' @param x A language or pairlist node. Note that these functions are
#'   barebones and do not perform any type checking.
#' @param car,newcar,cdr,newcdr The new CAR or CDR for the node. These
#'   can be any R objects.
#' @param newtag The new tag for the node. This should be a symbol.
#' @return Setters like `node_poke_car()` invisibly return `x` modified
#'   in place. Getters return the requested node component.
#' @seealso [duplicate()] for creating copy-safe objects and
#'   [base::pairlist()] for an easier way of creating a linked list of
#'   nodes.
#' @keywords internal
#' @export
new_node <- function(car, cdr = NULL) {
  .Call(ffi_new_node, car, cdr)
}

#' @rdname new_node
#' @export
node_car <- function(x) {
  .Call(ffi_node_car, x)
}
#' @rdname new_node
#' @export
node_cdr <- function(x) {
  .Call(ffi_node_cdr, x)
}
#' @rdname new_node
#' @export
node_caar <- function(x) {
  .Call(ffi_node_caar, x)
}
#' @rdname new_node
#' @export
node_cadr <- function(x) {
  .Call(ffi_node_cadr, x)
}
#' @rdname new_node
#' @export
node_cdar <- function(x) {
  .Call(ffi_node_cdar, x)
}
#' @rdname new_node
#' @export
node_cddr <- function(x) {
  .Call(ffi_node_cddr, x)
}

#' @rdname new_node
#' @export
node_poke_car <- function(x, newcar) {
  invisible(.Call(ffi_node_poke_car, x, newcar))
}
#' @rdname new_node
#' @export
node_poke_cdr <- function(x, newcdr) {
  invisible(.Call(ffi_node_poke_cdr, x, newcdr))
}
#' @rdname new_node
#' @export
node_poke_caar <- function(x, newcar) {
  invisible(.Call(ffi_node_poke_caar, x, newcar))
}
#' @rdname new_node
#' @export
node_poke_cadr <- function(x, newcar) {
  invisible(.Call(ffi_node_poke_cadr, x, newcar))
}
#' @rdname new_node
#' @export
node_poke_cdar <- function(x, newcdr) {
  invisible(.Call(ffi_node_poke_cdar, x, newcdr))
}
#' @rdname new_node
#' @export
node_poke_cddr <- function(x, newcdr) {
  invisible(.Call(ffi_node_poke_cddr, x, newcdr))
}

node_get <- function(node, i) {
  if (i < 1L) {
    abort("`i` must be an integer greater than 0.")
  }
  while (i > 1L) {
    node <- node_cdr(node)
    i <- i - 1L
  }
  node
}
node_get_car <- function(node, i) {
  node_car(node_get(node, i))
}

#' @rdname new_node
#' @export
node_tag <- function(x) {
  .Call(ffi_node_tag, x)
}
#' @rdname new_node
#' @export
node_poke_tag <- function(x, newtag) {
  invisible(.Call(ffi_node_poke_tag, x, newtag))
}

#' Is object a node or pairlist?
#'
#' @description
#'
#' * `is_pairlist()` checks that `x` has type `pairlist`.
#'
#' * `is_node()` checks that `x` has type `pairlist` or `language`.
#'    It tests whether `x` is a node that has a CAR and a CDR,
#'    including callable nodes (language objects).
#'
#' * `is_node_list()` checks that `x` has type `pairlist` or `NULL`.
#'   `NULL` is the empty node list.
#'
#'
#' @section Life cycle:
#'
#' These functions are experimental. We are still figuring out a good
#' naming convention to refer to the different lisp-like lists in R.
#'
#' @param x Object to test.
#' @seealso [is_call()] tests for language nodes.
#' @keywords internal
#' @export
is_pairlist <- function(x) {
  typeof(x) == "pairlist"
}
#' @rdname is_pairlist
#' @export
is_node <- function(x) {
  typeof(x) %in% c("pairlist", "language")
}
#' @rdname is_pairlist
#' @export
is_node_list <- function(x) {
  typeof(x) %in% c("pairlist", "NULL")
}

# Shallow copy of node trees
node_tree_clone <- function(x) {
  .Call(ffi_node_tree_clone, x);
}

node_walk <- function(.x, .f, ...) {
  cur <- .x
  while (!is.null(cur)) {
    .f(cur, ...)
    cur <- node_cdr(cur)
  }
  NULL
}
node_walk_nonnull <- function(.x, .f, ...) {
  cur <- .x
  out <- NULL
  while (!is.null(cur) && is.null(out)) {
    out <- .f(cur, ...)
    cur <- node_cdr(cur)
  }
  out
}
node_walk_last <- function(.x, .f, ...) {
  cur <- .x
  while (!is.null(node_cdr(cur))) {
    cur <- node_cdr(cur)
  }
  .f(cur, ...)
}

node_append <- function(.x, .y) {
  node_walk_last(.x, function(l) node_poke_cdr(l, .y))
  .x
}

node_list_reverse <- function(x) {
  .Call(ffi_pairlist_rev, x)
}


#' Create a new call from components
#'
#' @param car The head of the call. It should be a
#'   [callable][is_callable] object: a symbol, call, or literal
#'   function.
#' @param cdr The tail of the call, i.e. a [pairlist][new_node] of
#'   arguments.
#'
#' @keywords internal
#' @export
new_call <- function(car, cdr = NULL) {
  .Call(ffi_new_call, car, cdr)
}
tidyverse/rlang documentation built on Oct. 31, 2024, 5:35 p.m.