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 \xXXnotation | 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 = )))