Skip to content

Commit ae288b3

Browse files
committed
Use Ark backtrace formatting
1 parent 77f2e98 commit ae288b3

File tree

1 file changed

+121
-1
lines changed

1 file changed

+121
-1
lines changed

crates/harp/src/modules/init.R

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,15 @@ safe_evalq <- function(expr, env) {
1717
handler <- function(cnd) {
1818
# Save backtrace in error value
1919
calls <- sys.calls()
20-
trace <- paste(format(calls), collapse = '\n')
20+
21+
# Remove handling context
22+
n <- length(calls)
23+
if (n > 2) {
24+
calls <- calls[-c(n - 1, n)]
25+
}
26+
27+
trace <- format_traceback(calls)
28+
trace <- paste(trace, collapse = '\n')
2129

2230
message <- conditionMessage(cnd)
2331

@@ -34,3 +42,115 @@ safe_evalq <- function(expr, env) {
3442
error = handler
3543
)
3644
}
45+
46+
# ?FIXME: Can we reuse the following in Ark?
47+
48+
#' @param traceback A list of calls.
49+
format_traceback <- function(traceback = list()) {
50+
n <- length(traceback)
51+
52+
# TODO: This implementation prints the traceback in the same ordering
53+
# as rlang, i.e. with call 1 on the stack being the first thing you
54+
# see. `traceback()` prints in the reverse order, so users may want a
55+
# way to reverse both our display ordering and rlang's (requiring a
56+
# new `rlang::format()` argument).
57+
58+
# Collect source location, if there is any
59+
srcrefs <- lapply(traceback, function(call) attr(call, "srcref"))
60+
srcrefs <- vapply(srcrefs, src_loc, FUN.VALUE = character(1))
61+
has_srcref <- nchar(srcrefs) != 0L
62+
srcrefs[has_srcref] <- vec_paste0(" at ", srcrefs[has_srcref])
63+
64+
# Converts to a list of quoted calls to a list of deparsd calls.
65+
# Respects global options `"traceback.max.lines"` and `"deparse.max.lines"`!
66+
traceback <- .traceback(traceback)
67+
68+
# Prepend the stack number to each deparsed call, padding multiline calls as needed,
69+
# and then collapse multiline calls into one line
70+
prefixes <- vec_paste0(seq_len(n), ". ")
71+
prefixes <- format(prefixes, justify = "right")
72+
73+
traceback <- mapply(prepend_prefix, traceback, prefixes, SIMPLIFY = FALSE)
74+
traceback <- lapply(traceback, function(lines) paste0(lines, collapse = "\n"))
75+
traceback <- as.character(traceback)
76+
77+
paste0(traceback, srcrefs)
78+
}
79+
80+
prepend_prefix <- function(lines, prefix) {
81+
n_lines <- length(lines)
82+
83+
if (n_lines == 0L) {
84+
return(lines)
85+
}
86+
87+
# First line gets the prefix
88+
line <- lines[[1L]]
89+
line <- vec_paste0(prefix, line)
90+
91+
# Other lines are padded with whitespace as needed
92+
padding <- strrep(" ", times = nchar(prefix))
93+
94+
lines <- lines[-1L]
95+
lines <- vec_paste0(padding, lines)
96+
97+
lines <- c(line, lines)
98+
99+
lines
100+
}
101+
102+
src_loc <- function(srcref) {
103+
# Adapted from `rlang:::src_loc()`
104+
if (is.null(srcref)) {
105+
return("")
106+
}
107+
108+
srcfile <- attr(srcref, "srcfile")
109+
if (is.null(srcfile)) {
110+
return("")
111+
}
112+
113+
# May be:
114+
# - An actual file path
115+
# - `""` for user defined functions in the console
116+
# - `"<text>"` for `parse()`d functions
117+
# We only try and display the source location for file paths
118+
file <- srcfile$filename
119+
if (identical(file, "") || identical(file, "<text>")) {
120+
return("")
121+
}
122+
123+
file_trimmed <- path_trim_prefix(file, 3L)
124+
125+
first_line <- srcref[[1L]]
126+
first_column <- srcref[[5L]]
127+
128+
# TODO: We could generate file hyperlinks here like `rlang:::src_loc()`
129+
paste0(file_trimmed, ":", first_line, ":", first_column)
130+
}
131+
132+
path_trim_prefix <- function(path, n) {
133+
# `rlang:::path_trim_prefix()`
134+
split <- strsplit(path, "/")[[1]]
135+
n_split <- length(split)
136+
137+
if (n_split <= n) {
138+
path
139+
} else {
140+
paste(split[seq(n_split - n + 1, n_split)], collapse = "/")
141+
}
142+
}
143+
144+
vec_paste0 <- function(..., collapse = NULL) {
145+
# Like `paste0()`, but avoids `paste0("prefix:", character())`
146+
# resulting in `"prefix:"` and instead recycles to size 0.
147+
# Assumes that inputs with size >0 would validly recycle to size 0.
148+
args <- list(...)
149+
150+
if (any(lengths(args) == 0L)) {
151+
character()
152+
} else {
153+
args <- c(args, list(collapse = collapse))
154+
do.call(paste0, args)
155+
}
156+
}

0 commit comments

Comments
 (0)