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 tokensx <- call('+', c(1, 2))
base::deparse(x)
#> [1] "+c(1, 2)"
rlang::expr_deparse(x)
#> [1] "+<dbl: 1, 2>"
constructive::deparse_call(x)
#> Error in `constructive::deparse_call()`:
#> ! `call` must only be made of symbols and syntactic literals
#> Caused by error in `deparse_call_impl()`:
#> ! Found element of type 'double' and length '2':
#> c(1, 2)
# this is different
y <- quote(+c(1, 2))
x[[2]]
#> [1] 1 2
y[[2]]
#> c(1, 2)deparse_call() never makes compromises to make code
more readable at the expense of accuracy.x <- quote(`*`(a + b, c))
base::deparse(x)
#> [1] "(a + b) * c"
rlang::expr_deparse(x)
#> [1] "(a + b) * c"
constructive::deparse_call(x)
#> `*`(a + b, c)
y <- quote((a + b) * c)
base::deparse(y)
#> [1] "(a + b) * c"
rlang::expr_deparse(y)
#> [1] "(a + b) * c"
constructive::deparse_call(y)
#> (a + b) * c
# x and y are different, parentheses are code!
x[[2]]
#> a + b
y[[2]]
#> (a + b)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.x <- call("[")
base::deparse(x)
#> [1] "NULL[]"
rlang::expr_deparse(x)
#> [1] "NULL[]"
constructive::deparse_call(x)
#> `[`()deparse_call() is more accurateWe 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 | +c(1, 2) | 
+<dbl: 1, 2> | 
Infix :: and ::: can only be called on
symbols | 
`::`(1, 2) | 
1::2 | 
1::2 | 
Infix $ and @ can only have a symbol
rhs | 
`$`("a", 1) | 
`$`("a", 1) | 
"a"$1 | 
Infix $ and @ create different calls when
rhs is symbol or string | 
a$"b" | 
a$b | 
a$"b" | 
| Binary ops cannot be used as prefixes | `*`(1) | 
*1 | 
`*`(1) | 
| Binary ops cannot be used infix with > 2 args | `*`(1, 2, 3) | 
`*`(1, 2, 3) | 
1 * 2 | 
| Binary ops cannot be used infix with empty args | `*`(1, ) | 
1 * | 
1 * | 
| Parentheses need function call notation if 0 arg | `(`() | 
(NULL) | 
(NULL) | 
| Parentheses need function call notation if > 1 arg | `(`(1, 2) | 
(1) | 
(1) | 
Calling = is different from passing an arg | 
list(`=`(x, 1)) | 
list((x = 1)) | 
list(x = 1) | 
| Precedence must be respected, but adding extra parentheses to respect precedence is not accurate | `-`(1 + 2) | 
-(1 + 2) | 
-1 + 2 | 
`+`(repeat { }, 1) | 
(repeat {}) + 1 | 
repeat { } + 1 | 
|
`<-`(x <- 1, 2) | 
(x <- 1) <- 2 | 
(x <- 1) <- 2 | 
|
`*`(a + b, c) | 
(a + b) * c | 
(a + b) * c | 
|
`+`(x, y)(z) | 
(x + y)(z) | 
`+`(x, y)(z) | 
|
`^`(1^2, 4) | 
(1^2)^4 | 
(1^2)^4 | 
|
`+`(1, 2 + 3) | 
1 + (2 + 3) | 
1 + (2 + 3) | 
|
| Brackets calling no arg is different from subsetting NULL | `[`() | 
NULL[] | 
NULL[] | 
| 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 | `[`(x) | 
x[] | 
x[] | 
| Brackets with an empty first arg need function call notation | `[`(, ) | 
[] | 
[] | 
| Brackets taking a call to a lower precedence op as a first arg need function call notation | `[`(a + b, 1) | 
(a + b)[1] | 
a + b[1] | 
| Invalid function definitions can be valid code | `function`(1, 2) | 
ERROR | SEGFAULT | 
`function`(1(2), 3) | 
function(1, 2) 3 | 
ERROR | |
| Curly braces need function call notation if they have empty args | `{`(1, ) | 
{1} | 
{1} | 
| Control flow constructs need function call notation if they’re used as callers | `if`(TRUE, { })(1) | 
(if (TRUE) {})(1) | 
`if`(TRUE, { })(1) | 
| Symbols with non syntactic names need backquotes | `*a*` | 
*a* | 
`*a*` | 
| This includes emojis | `\xf0\x9f\x90\xb6` | 
🐶 | 
`🐶` | 
deparse_call() is clearerIn 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 | '"oh" "hey" "there"' | 
"\"oh\" \"hey\" \"there\"" | 
"\"oh\" \"hey\" \"there\"" | 
| Raw strings make more complex strings more readable | r"["oh"\'hey'\"there"]" | 
"\"oh\"\\'hey'\\\"there\"" | 
"\"oh\"\\'hey'\\\"there\"" | 
Homoglyphs are dangerous, we can use the \U{XX}
notation | 
"\U{410} \U{A0} A" | 
"А   A" | 
"А   A" | 
For symbols we need the \xXX notation | 
c(`\xd0\x90`, "\U{A0}" = 1) | 
c(А, ` ` = 1) | 
c(А, ` ` = 1) | 
| Emojis depend on font so are ambiguous | "\U{1F436}" | 
"🐶" | 
"🐶" | 
deparse_call() fails rather than making things upx <- call("(", -1)
base::deparse(x)
#> [1] "(-1)"
rlang::expr_deparse(x)
#> [1] "(-1)"
constructive::deparse_call(x)
#> Error in `constructive::deparse_call()`:
#> ! `call` must only be made of symbols and syntactic literals
#> Caused by error in `deparse_call_impl()`:
#> ! Found element of type 'double' and length '1':
#> -1
# this is different! `-` is code!
y <- quote((-1))
base::deparse(y)
#> [1] "(-1)"
rlang::expr_deparse(y)
#> [1] "(-1)"
constructive::deparse_call(y)
#> (-1)
x <- call("fun", quote(expr = ))
base::deparse(x)
#> [1] "fun()"
rlang::expr_deparse(x)
#> [1] "fun()"
constructive::deparse_call(x) # this is wrong!
#> Error in `constructive::deparse_call()`:
#> ! `call` must only be made of symbols and syntactic literals
#> Caused by error in `deparse_call_impl()`:
#> ! Found empty symbol used as sole argument of a function:
#> as.call(list(quote(fun), quote(expr = )))
# no agument and 1 missing argument is not the same!
y <- call("fun")
base::deparse(y)
#> [1] "fun()"
rlang::expr_deparse(y)
#> [1] "fun()"
constructive::deparse_call(y)
#> fun()
x <- call("!", quote(expr = ))
base::deparse(x)
#> [1] "!"
rlang::expr_deparse(x)
#> [1] "!"
constructive::deparse_call(x)
#> Error in `constructive::deparse_call()`:
#> ! `call` must only be made of symbols and syntactic literals
#> Caused by error in `deparse_call_impl()`:
#> ! Found empty symbol used as sole argument of a function:
#> as.call(list(quote(`!`), quote(expr = )))