---
title: "deparse_call"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{deparse_call}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
```
TODO: reorganize, put failing alternatives on top, then really wrong stuff, then inaccuracies then readability.
`constructive::deparse_call()` converts calls (language objects) to code.
It is an alternative to `base::deparse()` or `rlang::expr_deparse()` with a slightly
different scope, and 3 main differences:
* `deparse_call()` faisl if the call is not syntactic (if it cannot be the output
of `parse(text=x)[[1]]`), for instance if its AST contains elements that are
not syntactic tokens
```{r, error = TRUE}
x <- call('+', c(1, 2))
base::deparse(x)
rlang::expr_deparse(x)
constructive::deparse_call(x)
# this is different
y <- quote(+c(1, 2))
x[[2]]
y[[2]]
```
* `deparse_call()` never makes compromises to make code more readable at the
expense of accuracy.
```{r}
x <- quote(`*`(a + b, c))
base::deparse(x)
rlang::expr_deparse(x)
constructive::deparse_call(x)
y <- quote((a + b) * c)
base::deparse(y)
rlang::expr_deparse(y)
constructive::deparse_call(y)
# x and y are different, parentheses are code!
x[[2]]
y[[2]]
```
* `deparse_call()` handles many more contrived cases. It strives to provide an
accurate syntactic representation for every possible syntactic language object,
however unprobable or unpractical they might be.
```{r}
x <- call("[")
base::deparse(x)
rlang::expr_deparse(x)
constructive::deparse_call(x)
```
```{r setup, echo= FALSE}
library(constructive)
#deparse_call <- function(x) gsub("`", "\\\\`", constructive::deparse_call(x))
deparse_call <- function(x) paste(sprintf("`` %s ``", constructive::deparse_call(x)), collapse = "
")
deparse <- function(x) paste(sprintf("`` %s ``", base::deparse(x)), collapse = "
")
expr_deparse <- function(x) paste(sprintf("`` %s ``", rlang::expr_deparse(x)), collapse = "
")
# deparse <- function(x) as_constructive_code(base::deparse(x))
# expr_deparse <- function(x) as_constructive_code(rlang::expr_deparse(x))
compare_deparse_call <- function(x)
identical(unclass(constructive::deparse_call(x)), base::deparse(x)) &&
identical(base::deparse(x), rlang::expr_deparse(x))
```
## `deparse_call()` is more accurate
We present more differences below, where at least one of the alternatives is
not deparsing faithfully.
| | deparse_call() | deparse() | expr_deparse() |
|-------------------|------------------------------|-----------------|------------------------|
| `call('+', c(1, 2))` cannot be obtained by parsing code | ERROR | `r deparse(call('+', c(1, 2)))` | `r expr_deparse(call('+', c(1, 2)))` |
| Infix `::` and `:::` can only be called on symbols | `r deparse_call(call("::", 1, 2))` | `r deparse(call("::", 1, 2))` | `r expr_deparse(call("::", 1, 2))` |
| Infix `$` and `@` can only have a symbol rhs | `r deparse_call(call("$", "a", 1))` | `r deparse(call("$", "a", 1))` | `r expr_deparse(call("$", "a", 1))` |
| Infix `$` and `@` create different calls when rhs is symbol or string | `r deparse_call(call("$", quote(a), "b"))` | `r deparse(call("$", quote(a), "b"))` | `r expr_deparse(call("$", quote(a), "b"))` |
| Binary ops cannot be used as prefixes | `r deparse_call(call("*", 1)) ` | `r deparse(call("*", 1)) ` | `r expr_deparse(call("*", 1)) ` |
| Binary ops cannot be used infix with > 2 args | `r deparse_call(call("*", 1, 2, 3)) ` | `r deparse(call("*", 1, 2, 3)) ` | `r expr_deparse(call("*", 1, 2, 3)) ` |
| Binary ops cannot be used infix with empty args | `r deparse_call(call("*", 1, quote(expr=))) ` | `r deparse(call("*", 1, quote(expr=))) ` | `r expr_deparse(call("*", 1, quote(expr=))) ` |
| Parentheses need function call notation if 0 arg | `r deparse_call(call("("))` | `r deparse(call("("))` | `r expr_deparse(call("("))` |
| Parentheses need function call notation if > 1 arg | `r deparse_call(call("(", 1, 2))` | `r deparse(call("(", 1, 2))` | `r expr_deparse(call("(", 1, 2))` |
| Calling `=` is different from passing an arg | `r deparse_call(substitute(list(X), list(X = call("=", quote(x), 1))))` | `r deparse(substitute(list(X), list(X = call("=", quote(x), 1))))` | `r expr_deparse(substitute(list(X), list(X = call("=", quote(x), 1))))` |
| Precedence must be respected, but adding extra parentheses to respect precedence is not accurate | `r deparse_call(str2lang("'-'(1+2)"))` | `r deparse(str2lang("'-'(1+2)"))` | `r expr_deparse(str2lang("'-'(1+2)"))` |
| | `r deparse_call(quote('+'(repeat { }, 1)))` | `r deparse(quote('+'(repeat { }, 1)))` | `r expr_deparse(quote('+'(repeat { }, 1)))` |
| | `r deparse_call(quote(1 -> x <- 2))` | `r deparse(quote(1 -> x <- 2))` | `r expr_deparse(quote(1 -> x <- 2))` |
| | `r deparse_call(str2lang("'*'('+'(a, b), c)"))` | `r deparse(str2lang("'*'('+'(a, b), c)"))` | `r expr_deparse(str2lang("'*'('+'(a, b), c)"))` |
| | `r deparse_call(str2lang("'+'(x, y)(z)"))` | `r deparse(str2lang("'+'(x, y)(z)"))` | `r expr_deparse(str2lang("'+'(x, y)(z)"))` |
| | `r deparse_call(str2lang("'^'('^'(1, 2), 4)"))` | `r deparse(str2lang("'^'('^'(1, 2), 4)"))` | `r expr_deparse(str2lang("'^'('^'(1, 2), 4)"))` |
| | `r deparse_call(str2lang("'+'(1, '+'(2, 3))"))` | `r deparse(str2lang("'+'(1, '+'(2, 3))"))` | `r expr_deparse(str2lang("'+'(1, '+'(2, 3))"))` |
| Brackets calling no arg is different from subsetting NULL | `r deparse_call(call("["))` | `r deparse(call("["))` | `r expr_deparse(call("["))` |
| Empty bracket syntax means doesn't mean no 2nd arg, it means 2nd arg is empty symbol, so for 1 arg we need function notation | `r deparse_call(call("[", quote(x)))` | `r deparse(call("[", quote(x)))` | `r expr_deparse(call("[", quote(x)))` |
| Brackets with an empty first arg need function call notation | `r deparse_call(call("[", quote(expr=), quote(expr=)))` | `r deparse(call("[", quote(expr=), quote(expr=)))` | `r expr_deparse(call("[", quote(expr=), quote(expr=)))` |
| Brackets taking a call to a lower precedence op as a first arg need function call notation | `r deparse_call(call("[", quote(a+b), 1))` | `r deparse(call("[", quote(a+b), 1))` | `r expr_deparse(call("[", quote(a+b), 1))` |
| Invalid function definitions can be valid code | `r deparse_call(call("function", 1,2))` | ERROR | SEGFAULT |
| | `r deparse_call(quote('function'(1(2), 3)))` | `r deparse(quote('function'(1(2), 3)))` | ERROR |
| Curly braces need function call notation if they have empty args | `r deparse_call(call("{", 1, quote(expr = )))` | `r deparse(call("{", 1, quote(expr = )))` | `r expr_deparse(call("{", 1, quote(expr = )))` |
| Control flow constructs need function call notation if they're used as callers | `r deparse_call(quote('if'(TRUE, { })(1)))` | `r deparse(quote('if'(TRUE, { })(1)))` | `r expr_deparse(quote('if'(TRUE, { })(1)))` |
| Symbols with non syntactic names need backquotes | `r deparse_call(as.symbol("*a*"))` | `r deparse(as.symbol("*a*"))` | `r expr_deparse(as.symbol("*a*"))` |
| This includes emojis | `r deparse_call(as.symbol("🐶"))` | `r deparse(as.symbol("🐶"))` | `r expr_deparse(as.symbol("🐶"))` |
## `deparse_call()` is clearer
In the following `base::deparse()` and `rlang::expr_deparse()` are not wrong,
but `constructive::deparse_call()` is clearer.
| | constructive::deparse_call() | base::deparse() | rlang::expr_deparse() |
|------------------------|------------------------|------------------------|------------------------|
| Simple quotes make strings that use double quotes more readable | `r deparse_call('"oh" "hey" "there"')` | `r deparse('"oh" "hey" "there"')` | `r expr_deparse('"oh" "hey" "there"')` |
| Raw strings make more complex strings more readable | `r deparse_call('"oh"\\\'hey\'\\"there"')` | `r deparse('"oh"\\\'hey\'\\"there"')` | `r expr_deparse('"oh"\\\'hey\'\\"there"')` |
| Homoglyphs are dangerous, we can use the `\U{XX}` notation | `r deparse_call("\U{410} \U{A0} A")` | `r deparse("\U{410} \U{A0} A")` | `r expr_deparse("\U{410} \U{A0} A")` |
| For symbols we need the `\xXX` notation| `r deparse_call(call("c", as.symbol("\U{410}"), "\U{A0}" = 1))` | `r deparse(call("c", as.symbol("\U{410}"), "\U{A0}" = 1))` | `r expr_deparse(call("c", as.symbol("\U{410}"), "\U{A0}" = 1))` |
| Emojis depend on font so are ambiguous | `r deparse_call("🐶")` | `r deparse("🐶")` | `r expr_deparse("🐶")` |
## `deparse_call()` fails rather than making things up
```{r, error = TRUE}
x <- call("(", -1)
base::deparse(x)
rlang::expr_deparse(x)
constructive::deparse_call(x)
# this is different! `-` is code!
y <- quote((-1))
base::deparse(y)
rlang::expr_deparse(y)
constructive::deparse_call(y)
x <- call("fun", quote(expr = ))
base::deparse(x)
rlang::expr_deparse(x)
constructive::deparse_call(x) # this is wrong!
# no agument and 1 missing argument is not the same!
y <- call("fun")
base::deparse(y)
rlang::expr_deparse(y)
constructive::deparse_call(y)
x <- call("!", quote(expr = ))
base::deparse(x)
rlang::expr_deparse(x)
constructive::deparse_call(x)
```