@@ -17,7 +17,15 @@ safe_evalq <- function(expr, env) {
17
17
handler <- function (cnd ) {
18
18
# Save backtrace in error value
19
19
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 ' )
21
29
22
30
message <- conditionMessage(cnd )
23
31
@@ -34,3 +42,115 @@ safe_evalq <- function(expr, env) {
34
42
error = handler
35
43
)
36
44
}
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