line_push <- function(line, text,
sticky = FALSE,
boundary = NULL,
width = NULL,
indent = 0L,
has_colour = FALSE) {
if (!length(line)) {
return(text)
}
check_string(line)
check_string(text)
width <- width %||% peek_option("width")
if (!has_overflown(line, text, width, has_colour)) {
return(paste0(line, text))
}
if (is_scalar_integer(boundary) && nchar(line) != boundary) {
first <- substr(line, 0L, boundary)
second <- substr(line, boundary + 1L, nchar(line))
# Trim trailing spaces after boundary
second <- trim_leading_spaces(second)
second <- paste0(spaces(indent), second)
if (sticky || !has_overflown(second, text, width, has_colour)) {
line <- trim_trailing_spaces(first)
text <- paste0(second, text)
} else {
text <- paste0(spaces(indent), text)
}
} else if (sticky) {
line <- paste0(line, text)
text <- chr()
} else {
line <- trim_trailing_spaces(line)
text <- paste0(spaces(indent), text)
}
c(line, text)
}
spaces <- function(ns) {
map_chr(ns, function(n) paste(rep(" ", n), collapse = ""))
}
is_spaces <- function(str) {
identical(str, spaces(nchar(str)))
}
has_overflown <- function(line, text, width, has_colour) {
if (has_colour) {
line <- strip_style(line)
text <- strip_style(text)
}
text <- trim_trailing_spaces(text)
nchar(line) + nchar(text) > width && !is_spaces(line)
}
trim_trailing_spaces <- function(line) {
sub(" *$", "", line)
}
trim_leading_spaces <- function(line) {
sub("^ *", "", line)
}
new_lines <- function(width = peek_option("width"),
max_elements = 5L,
deparser = sexp_deparse) {
width <- width %||% 60L
stopifnot(
is_integerish(width, n = 1),
is_null(max_elements) || is_scalar_integerish(max_elements)
)
r6lite(
deparse = function(self, x) {
deparser(x, lines = self)
},
width = width,
max_elements = max_elements,
boundary = NULL,
next_sticky = FALSE,
indent = 0L,
indent_status = pairlist(),
next_indent_sticky = FALSE,
has_colour = FALSE,
lines = chr(),
last_line = chr(),
get_lines = function(self) {
c(self$lines, self$last_line)
},
get_indent = function(self) {
if (self$indent < 0) {
warn("Internal error: Negative indent while deparsing")
0L
} else {
self$indent
}
},
push = function(self, lines) {
stopifnot(is_character(lines))
for (line in lines) {
self$push_one(line)
}
self
},
push_one = function(self, line) {
line <- line_push(self$last_line, line,
sticky = self$next_sticky,
boundary = self$boundary,
width = self$width,
indent = self$get_indent(),
has_colour = self$has_colour
)
n <- length(line)
if (n > 1) {
self$lines <- c(self$lines, line[-n])
self$last_line <- line[[n]]
self$boundary <- NULL
self$next_indent_sticky <- FALSE
} else if (n) {
self$last_line <- line
if (self$next_sticky) {
self$boundary <- nchar(line)
}
}
self$next_sticky <- FALSE
self
},
push_newline = function(self) {
self$lines <- c(self$lines, self$last_line)
self$last_line <- spaces(self$get_indent())
self$next_sticky <- FALSE
self$next_indent_sticky <- FALSE
self
},
push_sticky = function(self, line) {
stopifnot(is_string(line))
self$next_sticky <- TRUE
self$push(line)
self$set_boundary()
self
},
make_next_sticky = function(self) {
self$next_sticky <- TRUE
self
},
set_boundary = function(self) {
self$boundary <- nchar(self$last_line)
self
},
increase_indent = function(self) {
status <- node_car(self$indent_status)
if (self$next_indent_sticky) {
node_poke_cadr(status, inc(node_cadr(status)))
} else {
self$indent <- self$indent + 2L
self$indent_status <- new_node(new_node(FALSE, new_node(0L, NULL)), self$indent_status)
self$next_indent_sticky <- TRUE
}
self
},
decrease_indent = function(self) {
status <- node_car(self$indent_status)
if (is_null(status)) {
warn("Internal error: Detected NULL `status` while deparsing")
return(self)
}
reset <- node_car(status)
n_sticky <- node_cadr(status)
# Decrease indent level only once for all the openers that were
# on a single line
if (!reset) {
self$indent <- self$indent - 2L
node_poke_car(status, TRUE)
self$next_indent_sticky <- FALSE
}
if (n_sticky >= 1L) {
node_poke_cadr(status, dec(n_sticky))
} else {
self$indent_status <- node_cdr(self$indent_status)
self$next_indent_sticky <- FALSE
}
self
}
)
}
fmls_deparse <- function(x, lines = new_lines()) {
lines$push_sticky("(")
lines$increase_indent()
while (!is_null(x)) {
sym_deparse(node_tag(x), lines)
car <- node_car(x)
if (!is_missing(car)) {
lines$push_sticky(" = ")
lines$make_next_sticky()
lines$deparse(node_car(x))
}
x <- node_cdr(x)
if (!is_null(x)) {
lines$push_sticky(", ")
}
}
lines$push_sticky(")")
lines$decrease_indent()
lines$get_lines()
}
fn_call_deparse <- function(x, lines = new_lines()) {
lines$push("function")
x <- node_cdr(x)
fmls_deparse(node_car(x), lines)
lines$push_sticky(" ")
lines$increase_indent()
x <- node_cdr(x)
lines$deparse(node_car(x))
lines$decrease_indent()
lines$get_lines()
}
fn_deparse <- function(x, lines) {
lines$push("<function")
fmls_deparse(fn_fmls(x), lines)
lines$push_sticky(" ")
lines$increase_indent()
lines$deparse(body(x))
lines$push_sticky(">")
lines$decrease_indent()
lines$get_lines()
}
while_deparse <- function(x, lines = new_lines()) {
x <- node_cdr(x)
lines$push("while (")
lines$deparse(node_car(x))
x <- node_cdr(x)
lines$push(") ")
lines$deparse(node_car(x))
lines$get_lines()
}
for_deparse <- function(x, lines = new_lines()) {
x <- node_cdr(x)
lines$push("for (")
lines$deparse(node_car(x))
x <- node_cdr(x)
lines$push(" in ")
lines$deparse(node_car(x))
x <- node_cdr(x)
lines$push(") ")
lines$deparse(node_car(x))
lines$get_lines()
}
repeat_deparse <- function(x, lines = new_lines()) {
lines$push("repeat ")
lines$deparse(node_cadr(x))
lines$get_lines()
}
next_deparse <- function(x, lines = new_lines()) {
lines$push("next")
lines$get_lines()
}
break_deparse <- function(x, lines = new_lines()) {
lines$push("break")
lines$get_lines()
}
if_deparse <- function(x, lines = new_lines()) {
x <- node_cdr(x)
lines$push("if (")
lines$deparse(node_car(x))
x <- node_cdr(x)
lines$push(") ")
lines$deparse(node_car(x))
x <- node_cdr(x)
if (!is_null(x)) {
lines$push(" else ")
lines$deparse(node_car(x))
}
lines$get_lines()
}
# Wrap if the call lower in the AST is not supposed to have
# precedence. This sort of AST cannot arise in parsed code but can
# occur in constructed calls.
operand_deparse <- function(x, parent, side, lines) {
wrap <- !call_has_precedence(x, parent, side)
if (wrap) {
lines$push("(")
lines$make_next_sticky()
}
lines$deparse(x)
if (wrap) {
lines$push_sticky(")")
}
}
binary_op_deparse <- function(x, lines = new_lines(), space = " ", sticky_rhs = FALSE) {
# Constructed call without second argument
if (is_null(node_cddr(x))) {
return(call_deparse(x, lines))
}
outer <- x;
op <- as_string(node_car(x))
x <- node_cdr(x)
operand_deparse(node_car(x), outer, "lhs", lines)
lines$push_sticky(paste0(space, op, space))
if (sticky_rhs) {
lines$make_next_sticky()
}
x <- node_cdr(x)
lines$increase_indent()
operand_deparse(node_car(x), outer, "rhs", lines)
lines$decrease_indent()
lines$get_lines()
}
spaced_op_deparse <- function(x, lines = new_lines()) {
binary_op_deparse(x, lines, space = " ")
}
unspaced_op_deparse <- function(x, lines = new_lines()) {
binary_op_deparse(x, lines, space = "")
}
tight_op_deparse <- function(x, lines = new_lines()) {
binary_op_deparse(x, lines, space = "", sticky_rhs = TRUE)
}
unary_op_deparse <- function(x, lines = new_lines()) {
# Constructed call without argument
if (is_null(node_cdr(x))) {
return(call_deparse(x, lines))
}
op <- as_string(node_car(x))
lines$push(op)
lines$deparse(node_cadr(x))
lines$get_lines()
}
unary_f_deparse <- function(x, lines = new_lines()) {
# Constructed call without argument
if (is_null(node_cdr(x))) {
return(call_deparse(x, lines))
}
lines$push("~")
rhs <- node_cadr(x)
if (!is_symbol(rhs) && !is_syntactic_literal(rhs)) {
lines$push(" ")
}
lines$deparse(rhs)
lines$get_lines()
}
brackets_deparse <- function(x, lines = new_lines()) {
x <- node_cdr(x)
lines$deparse(node_car(x))
args_deparse(node_cdr(x), lines, delims = c("[", "]"))
lines$get_lines()
}
brackets2_deparse <- function(x, lines = new_lines()) {
x <- node_cdr(x)
lines$deparse(node_car(x))
args_deparse(node_cdr(x), lines, delims = c("[[", "]]"))
lines$get_lines()
}
parens_deparse <- function(x, lines = new_lines()) {
lines$push("(")
lines$deparse(node_cadr(x))
lines$push(")")
lines$get_lines()
}
braces_deparse <- function(x, lines = new_lines()) {
lines$push("{")
lines$increase_indent()
x <- node_cdr(x)
# No need for a newline if the block is empty
if (is_null(x)) {
lines$push(" }")
return(lines$get_lines())
}
while (!is_null(x)) {
lines$push_newline()
lines$deparse(node_car(x))
x <- node_cdr(x)
}
lines$decrease_indent()
lines$push_newline()
lines$push("}")
lines$get_lines()
}
embrace_deparse <- function(x, lines = new_lines()) {
lines$push("{{ ")
lines$increase_indent()
sym <- node_cadr(node_cadr(x))
lines$deparse(sym)
lines$push(" }}")
lines$decrease_indent()
lines$get_lines()
}
sym_deparse <- function(x, lines = new_lines()) {
str <- encodeString(as_string(x))
if (needs_backticks(str)) {
str <- sprintf("`%s`", str)
}
lines$push(str)$get_lines()
}
args_deparse <- function(x, lines = new_lines(), delims = c("(", ")")) {
stopifnot(is_character(delims, n = 2))
lines$push_sticky(delims[[1]])
lines$increase_indent()
while (!is_null(x)) {
tag <- node_tag(x)
if (!is_null(tag)) {
sym_deparse(tag, lines = lines)
lines$push_sticky(" = ")
lines$make_next_sticky()
}
lines$deparse(node_car(x))
x <- node_cdr(x)
if (!is_null(x)) {
lines$push_sticky(", ")
}
}
lines$push_sticky(delims[[2]])
lines$decrease_indent()
lines$get_lines()
}
call_deparse <- function(x, lines = new_lines()) {
car <- node_car(x)
type <- call_delimited_type(car)
switch(type,
parens = {
car <- call("(", car)
lines$deparse(car)
},
backticks = {
lines$deparse(node_car(car))
args_deparse(node_cdr(car), lines)
},
lines$deparse(car)
)
args_deparse(node_cdr(x), lines)
}
call_delimited_type <- function(call) {
if (!is_call(call)) {
return("none")
}
op <- call_parse_type(call)
if (op == "") {
return("none")
}
switch (op,
`function` =
"parens",
`while` = ,
`for` = ,
`repeat` = ,
`if` = ,
`?` = ,
`<-` = ,
`<<-` = ,
`=` = ,
`:=` = ,
`~` = ,
`|` = ,
`||` = ,
`&` = ,
`&&` = ,
`>` = ,
`>=` = ,
`<` = ,
`<=` = ,
`==` = ,
`!=` = ,
`+` = ,
`-` = ,
`*` = ,
`/` = ,
`%%` = ,
`special` = ,
`:` = ,
`^` = ,
`?unary` = ,
`~unary` = ,
`!` = ,
`!!!` = ,
`!!` = ,
`+unary` = ,
`-unary` =
"backticks",
`$` = ,
`@` = ,
`::` = ,
`:::` = ,
`[` = ,
`[[` = ,
`(` = ,
`{` = ,
`{{` =
"none",
abort("Internal error: Unexpected operator while deparsing")
)
}
op_deparse <- function(op, x, lines) {
deparser <- switch (op,
`function` = fn_call_deparse,
`while` = while_deparse,
`for` = for_deparse,
`repeat` = repeat_deparse,
`if` = if_deparse,
`next` = next_deparse,
`break` = break_deparse,
`?` = ,
`<-` = ,
`<<-` = ,
`=` = ,
`:=` = ,
`~` = ,
`|` = ,
`||` = ,
`&` = ,
`&&` = ,
`>` = ,
`>=` = ,
`<` = ,
`<=` = ,
`==` = ,
`!=` = ,
`+` = ,
`-` = ,
`*` = ,
`/` = ,
`%%` = ,
`special` = spaced_op_deparse,
`:` = ,
`^` = ,
`$` = ,
`@` = unspaced_op_deparse,
`::` = ,
`:::` = tight_op_deparse,
`~unary` = unary_f_deparse,
`?unary` = ,
`!` = ,
`!!!` = ,
`!!` = ,
`+unary` = ,
`-unary` = unary_op_deparse,
`[` = brackets_deparse,
`[[` = brackets2_deparse,
`(` = parens_deparse,
`{` = braces_deparse,
`{{` = embrace_deparse,
abort("Internal error: Unexpected operator while deparsing")
)
deparser(x, lines)
lines$get_lines()
}
call_deparser <- function(x) {
op <- call_parse_type(x)
if (op != "") {
function(x, lines) op_deparse(op, x, lines)
} else {
call_deparse
}
}
atom_elements <- function(x) {
elts <- as.character(x)
na_pos <- are_na(x) & !is.nan(x)
elts[na_pos] <- "NA"
elts[!na_pos] <- switch (typeof(x),
integer = paste0(elts[!na_pos], "L"),
character = map_chr(elts[!na_pos], deparse),
elts[!na_pos]
)
elts
}
is_scalar_deparsable <- function(x) {
typeof(x) != "raw" && length(x) == 1 && !is_named(x)
}
atom_deparse <- function(x, lines = new_lines()) {
if (is_scalar_deparsable(x)) {
lines$push(deparse(x))
return(NULL)
}
max_elements <- lines$max_elements
truncated <- !is.null(max_elements) && length(x) > max_elements
if (truncated) {
x <- .subset(x, seq_len(max_elements))
}
lines$push(paste0("<", rlang_type_sum(x), ": "))
lines$increase_indent()
elts <- atom_elements(x)
nms <- deparsed_names(x)
n <- length(elts)
for (i in seq_len(n)) {
nm <- nms[[i]]
if (nzchar(nm)) {
lines$push(paste0(nm, " = "))
lines$make_next_sticky()
}
lines$push(elts[[i]])
if (i < n || truncated) {
lines$push_sticky(", ")
}
}
if (truncated) {
lines$push("...")
}
lines$push_sticky(">")
lines$decrease_indent()
lines$get_lines()
}
list_deparse <- function(x, lines = new_lines()) {
if (!length(x) && !is_null(names(x))) {
lines$push("<named list>")
return(lines$get_lines())
}
max_elements <- lines$max_elements
lines$push(paste0("<list: "))
lines$increase_indent()
truncated <- !is.null(max_elements) && length(x) > max_elements
if (truncated) {
x <- .subset(x, seq_len(max_elements))
}
nms <- deparsed_names(x)
n <- length(x)
for (i in seq_len(n)) {
nm <- nms[[i]]
if (nzchar(nm)) {
lines$push(paste0(nm, " = "))
lines$make_next_sticky()
}
lines$deparse(x[[i]])
if (i < n || truncated) {
lines$push_sticky(", ")
}
}
if (truncated) {
lines$push("...")
}
lines$push_sticky(">")
lines$decrease_indent()
lines$get_lines()
}
s3_deparse <- function(x, lines = new_lines()) {
lines$push(paste0("<", rlang_type_sum(x), ">"))
lines$get_lines()
}
literal_deparser <- function(type) {
function(x, lines = new_lines()) {
lines$push(paste0("<", type, ">"))
}
}
default_deparse <- function(x, lines = new_lines()) {
lines$push(deparse(x, control = "keepInteger"))
lines$get_lines()
}
sexp_deparse <- function(x, lines = new_lines()) {
if (is.object(x)) {
return(s3_deparse(x, lines))
}
deparser <- switch (typeof(x),
symbol = sym_deparse,
language = call_deparser(x),
closure = fn_deparse,
`...` = literal_deparser("..."),
any = literal_deparser("any"),
environment = literal_deparser("environment"),
externalptr = literal_deparser("pointer"),
promise = literal_deparser("promise"),
weakref = literal_deparser("weakref"),
logical = ,
integer = ,
double = ,
complex = ,
character = ,
raw = atom_deparse,
list = list_deparse,
default_deparse
)
deparser(x, lines)
lines$get_lines()
}
needs_backticks <- function(str) {
if (!is_string(str)) {
str <- as_string(str)
}
n <- nchar(str)
if (!n) {
return(FALSE)
}
if (str %in% reserved_words) {
return(TRUE)
}
start <- substr(str, 1, 1)
if (!grepl("[[:alpha:].]", start)) {
return(TRUE)
}
if (n == 1) {
return(FALSE)
}
remaining <- substr(str, 2, n)
# .0 double literals
if (start == "." && grepl("^[[:digit:]]", remaining)) {
return(TRUE)
}
grepl("[^[:alnum:]_.]", remaining)
}
# From gram.y
reserved_words <- c(
"NULL",
"NA",
"TRUE",
"FALSE",
"Inf",
"NaN",
"NA_integer_",
"NA_real_",
"NA_character_",
"NA_complex_",
"function",
"while",
"repeat",
"for",
"if",
"in",
"else",
"next",
"break"
)
deparsed_names <- function(x) {
encodeString(names2(x))
}
#' Create a default name for an R object
#'
#' @description
#'
#' `as_label()` transforms R objects into a short, human-readable
#' description. You can use labels to:
#'
#' * Display an object in a concise way, for example to labellise axes
#' in a graphical plot.
#'
#' * Give default names to columns in a data frame. In this case,
#' labelling is the first step before name repair.
#'
#' See also [as_name()] for transforming symbols back to a
#' string. Unlike `as_label()`, `as_name()` is a well defined
#' operation that guarantees the roundtrip symbol -> string ->
#' symbol.
#'
#' In general, if you don't know for sure what kind of object you're
#' dealing with (a call, a symbol, an unquoted constant), use
#' `as_label()` and make no assumption about the resulting string. If
#' you know you have a symbol and need the name of the object it
#' refers to, use [as_name()]. For instance, use `as_label()` with
#' objects captured with `enquo()` and `as_name()` with symbols
#' captured with `ensym()`.
#'
#' @param x An object.
#'
#' @section Transformation to string:
#'
#' * Quosures are [squashed][quo_squash] before being labelled.
#' * Symbols are transformed to string with `as_string()`.
#' * Calls are abbreviated.
#' * Numbers are represented as such.
#' * Other constants are represented by their type, such as `<dbl>`
#' or `<data.frame>`.
#'
#' @seealso [as_name()] for transforming symbols back to a string
#' deterministically.
#'
#' @examples
#' # as_label() is useful with quoted expressions:
#' as_label(expr(foo(bar)))
#'
#' as_label(expr(foobar))
#'
#' # It works with any R object. This is also useful for quoted
#' # arguments because the user might unquote constant objects:
#' as_label(1:3)
#'
#' as_label(base::list)
#' @export
as_label <- function(x) {
x <- quo_squash(x)
if (is_missing(x)) {
return("<empty>")
}
switch(
typeof(x),
NULL = "NULL",
symbol = as_string(x),
language = {
if (is_data_pronoun(x)) {
return(data_pronoun_name(x) %||% "<unknown>")
}
if (use_as_label_infix() && infix_overflows(x)) {
return(as_label_infix(x))
}
name <- deparse_one(x)
name <- gsub("\n.*$", "...", name)
name
},
if (is_bare_atomic(x, n = 1)) {
name <- expr_text(x)
name <- gsub("\n.*$", "...", name)
name
} else {
paste0("<", rlang_type_sum(x), ">")
}
)
}
# Work around a slowdown caused by `infix_overflows()`
# https://github.com/tidyverse/dplyr/issues/6674
# https://github.com/tidyverse/dplyr/issues/6681
use_as_label_infix <- function() {
!is_false(peek_option("rlang:::use_as_label_infix"))
}
infix_overflows <- function(x) {
call_print_type(x) %in% c("infix", "subset") &&
length(expr_deparse(x, width = 60)) > 1
}
as_label_infix <- function(x) {
# Shorten the expression if we're too long. Preserve the left side
# if possible.
infix_n <- nchar_infix(x)
dots_n <- 3
left_width <- 60 - infix_n - dots_n
left <- expr_deparse(x[[2]], width = left_width)
if (length(left) > 1 || nchar(left) > left_width) {
x[[2]] <- quote(...)
left_n <- dots_n
} else {
left_n <- nchar(left)
}
right_width <- 60 - left_n - infix_n
right <- expr_deparse(x[[3]], width = right_width)
if (length(right) > 1 || nchar(right) > right_width) {
x[[3]] <- quote(...)
}
out <- expr_deparse(x, width = 60)
# In case something went wrong
if (length(out) > 1) {
if (testing()) {
abort("Deparsed `out` can't be multiline.", .internal = TRUE)
}
paste(out[[1]], "...")
} else {
out
}
}
nchar_infix <- function(x) {
x[c(2, 3)] <- 1
nchar(expr_deparse(x)) - 2
}
#' Extract names from symbols
#'
#' @description
#'
#' `as_name()` converts [symbols][sym] to character strings. The
#' conversion is deterministic. That is, the roundtrip `symbol -> name
#' -> symbol` always gives the same result.
#'
#' - Use `as_name()` when you need to transform a symbol to a string
#' to _refer_ to an object by its name.
#'
#' - Use [as_label()] when you need to transform any kind of object to
#' a string to _represent_ that object with a short description.
#'
#' @param x A string or symbol, possibly wrapped in a [quosure][quosure].
#' If a string, the attributes are removed, if any.
#' @return A character vector of length 1.
#'
#' @details
#' `rlang::as_name()` is the _opposite_ of [base::as.name()]. If
#' you're writing base R code, we recommend using [base::as.symbol()]
#' which is an alias of `as.name()` that follows a more modern
#' terminology (R types instead of S modes).
#'
#' @seealso [as_label()] for converting any object to a single string
#' suitable as a label. [as_string()] for a lower-level version that
#' doesn't unwrap quosures.
#'
#' @examples
#' # Let's create some symbols:
#' foo <- quote(foo)
#' bar <- sym("bar")
#'
#' # as_name() converts symbols to strings:
#' foo
#' as_name(foo)
#'
#' typeof(bar)
#' typeof(as_name(bar))
#'
#' # as_name() unwraps quosured symbols automatically:
#' as_name(quo(foo))
#' @export
as_name <- function(x) {
if (is_quosure(x)) {
x <- quo_get_expr(x)
}
as_string(x)
}
call_deparse_highlight <- function(call, arg) {
stopifnot(is_call(call))
if (!is_string(arg)) {
arg <- NULL
}
local_error_highlight()
if (!is_call_simple(call) || call_print_fine_type(call) != "call") {
return(format_code_unquoted(as_label(call)))
}
names <- names(call)
if (!is_null(arg) && arg %in% names) {
# Simply remove other arguments for now
call <- call[c(1, match(arg, names))]
args_list <- sprintf("%s = %s", arg, as_label(call[[arg]]))
args_list <- format_arg_unquoted(args_list)
} else {
args_list <- call
args_list[[1]] <- quote(F)
args_list <- as_label(args_list)
args_list <- substring(args_list, 3, nchar(args_list) - 1)
}
head <- call[[1]]
if (is_symbol(head)) {
fn <- sym_text(head)
} else {
fn <- as_label(head)
}
open <- format_code_unquoted(sprintf("%s(", fn))
close <- format_code_unquoted(")")
paste0(open, args_list, close)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.