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 ofparse(text=x)[[1]]
), for instance if its AST contains elements that are not syntactic tokens
x <- 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()` at constructive/R/deparse_call.R:105:5:
#> ! 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 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 | +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 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 | '"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 up
x <- 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()` at constructive/R/deparse_call.R:105:5:
#> ! 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()` at constructive/R/deparse_call.R:49:3:
#> ! 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()` at constructive/R/deparse_call.R:49:3:
#> ! Found empty symbol used as sole argument of a function:
#> as.call(list(quote(`!`), quote(expr = )))