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 tokensx <- 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.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.x <- call("[") base::deparse(x) rlang::expr_deparse(x) constructive::deparse_call(x)
library(constructive) #deparse_call <- function(x) gsub("`", "\\\\`", constructive::deparse_call(x)) deparse_call <- function(x) paste(sprintf("`` %s ``", constructive::deparse_call(x)), collapse = "<br>") deparse <- function(x) paste(sprintf("`` %s ``", base::deparse(x)), collapse = "<br>") expr_deparse <- function(x) paste(sprintf("`` %s ``", rlang::expr_deparse(x)), collapse = "<br>") # 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 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 | 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 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 | 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 upx <- 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)
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.